Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

WPF Form Designer Prototype (MVVM)

Rate me:
Please Sign up or sign in to vote.
4.83/5 (31 votes)
3 Apr 2013CPOL9 min read 78.9K   3.4K   108   22
Form designer with editable display properties and bindings. Generates XAML forms.

Introduction

The main purpose of this article is to present a prototype for a basic form designer in WPF/C# with the following characteristics and functionality:

  • Can be used to create data entry forms generating XAML that can be stored in a file system or in a database and then reloaded back again. 
  • Dragging and dropping any control from the toolbox to the design surface
  • Re-sizing/merging, removing controls 
  • Add/Remove rows from the form  
  • Setting data bindings for each type of control through a properties window 
  • Setting display properties for each control through a properties window, filtering properties (showing only desired ones)  

This is just a sample of the type of forms that can be created with this application:

Image 1

Background  

This prototype was the result of a year-effort after giving up trying to find a free hostable Design Surface to create WPF forms, similar to the one available in Windows Forms. Unfortunately Visual Studio Cider designer is not open so developers can't include it in third party applications.

The chosen design pattern for this prototype was MVVM. However, code-behind had to be used in certain areas of the application due to heavy UI manipulation. I also wanted to point out that the real application was implemented using Avalon Dock  library to provide IDE style docking, but it was removed from this prototype to reduce the scope and complexity of the article.

The main concept behind this application is to build a fixed-column Grid where rows can be added dynamically and where controls can be dragged from a Toolbox to each grid cell. Right-clicking each control and then -> 'Properties' or single left click will display the chosen control properties  in the properties panel.

Using the code 

Here is the VS 2010 Solution: Download WPFFormDesigner.zip

There are three main external libraries/projects used in this sample application:

  1. WPF Property Grid - Provides all the functionality for displaying  control properties, with lots of customizable features, like grouping, filtering, creating extended editors, etc 
  2. Simple MVVM Toolkit - Very easy to understand MVVM framework, provides message bus capability to communicate between ViewModels, simplifies property notifications 
  3. FluidKit  - Used only for Drag/Drop enhancements and simplification.

This is how the main application looks like:

Image 2

The main project in the Visual Studio solution is called WPFFormDesigner. All the View related files are located at the Root level.

Image 3

The name of the main View files are self-explanatory, an can be  easily identified in the application screenshot above. Like the FormDesignerSurface , the PropertiesView, MainWindow and ToolboxView.  Most of this views have a corresponding ViewModel located in the ViewModel folder. In this case the MainWindow is the main application Window.

Grid Manipulation

Most of the Grid manipulation happens in the WPFControlUtils\FormDesignerGridExtensions.cs class. This class contains extension methods to manipulate the grid like adding rows, removing rows, readjusting columns, etc.

 Drag & Drop

The drag and drop related files using the FluidKit libraries are located under: WPFControlUtils\DragDrop folder.  

Adding rows

The initial row must be added through the 'Add Row' button.

Image 4

The subsequent rows can be added below by using the 'insert below icon':

Image 5

Removing rows

Rows can be removed by clicking on the red (-) circle button shown in the image above.

Contextual Menus  

Image 6

By right-clicking a dragged control in the grid we get three options:

  1. Show Properties - Will set this as the selected control in the property grid 
  2. Remove Control - Will remove the control from this cell and will prepare the cell to be drag destination again 
  3. Merge Cells - This is an experimental feature, it only works setting the ColumnSpan of the selected control to 6 which is the number of ColumnDefinitions. This is useful when dragging an expander for example, we just merge the cells and the expander will occupy the whole row:
    Image 7

Notice also that the Expander will also contain 6 internal columns where controls can be added.

Control Re-sizing Adorners 

Again, I'm not trying to re-invent the wheel here, for Control resizing I found a good alternative in Denis Vuyka article: WPF. Simple adorner usage with drag and resize operations .

This allows you to display an adorner around the selected control so it can be resized. 


Image 8 

The resize logic can be found in the FormDesignerSurface.xaml.cs , look for this line:

C#
<span style="font-size: 9pt;">        </span>#region<span style="font-size: 9pt;"> Resizing Adorner Section</span> 

And it uses Denis's WPFFormDesigner.WPFControlUtils.ResizingAdorner.cs class. 

Experimental Serialize option 

Image 9 

The 'Serialize' button in the Design surface will open a floating window with the XAML generated for the form. This is useful for troubleshooting and making sure the form is correct. It uses the XamlSerializedOutputWindow.xaml control to display the XAML.  Notice that the right panel is empty, this is meant to display the 'cleaned' XAML, after XSLT cleanup. The one that will be rendered to the user for data entry. 

Main Menu

The main 'File' menu contains three options:

Image 10

  1. Open - Will display a dialog to browse for an existing XAML file located in users computer 
  2. New - Will display a new form with no rows ready to be created 
  3. Save  - Will display a dialog to save the existing form to a selected location 

XAML Serializer  

Initially the normal XAMLWriter.Save method was used to serialize, but it turned out to be very limited and was not serializing a lot of the MarkupExtensions used in data bindings.  For that reason, a set of custom XAML Serialization classes were created under the XamlSerializer folder based on AlexDov's XamlWriter and Bindings Serialization CodeProject article

Image 11

It is important to notice that every class that uses the XAMLSerializer needs to be initialized with the following (MultiBinding registration is optional, if form does not define a multibinding):

C#
EditorHelper.Register<BindingExpression, BindingConvertor>();
//Serialize Multibinding too!!
EditorHelper.Register<MultiBindingExpression,MultiBindingConvertor>();  

The EditorHelper class contains a static Register method, where it registers all the different MarkupExtensions we want to serialize, like SelectedValue, SelectedItem, SelectedIndex, etc:

C#
internal static class EditorHelper
{
    public static void Register<T, TC>()
    {
        Attribute[] attr = new Attribute[1];
        TypeConverterAttribute vConv = new TypeConverterAttribute(typeof (TC));
        attr[0] = vConv;
        TypeDescriptor.AddAttributes(typeof (T), attr);
        //Register ItemsControl provider to serialize ComboBox ItemsSource binding!!!
        ItemsControlTypeDescriptionProvider.Register();
        SelectedValueTypeDescriptionProvider.Register();
        SelectedItemTypeDescriptionProvider.Register();
        SelectedIndexTypeDescriptionProvider.Register();
        //Item Template Selector for ListBox collection
        //Templates
        ListBoxItemTemplateSelectorTypeDescriptionProvider.Register();
    }
} 

Control Properties

Every control in the toolbox has a corresponding Proxy class under the WPFPropertyGridProxies folder. This proxy classes are just wrapper classes that only expose certain properties of that control so they can be shown in the Properties View. In this sample application we just expose basic properties like Height, Width, Name, Background, etc. When right-clicking -> 'Properties' or left clicking on a control, the WPFPropertyGridProxyConverter will be invoked which will convert the selected control into its related proxy:

C#
if (value is TextBox)
   return new TextBoxProxy(value as TextBox);

if (value is Label)
    return new LabelProxy(value as Label);

if (value is CheckBox)
    return new CheckBoxProxy(value as CheckBox); 

Extended templates

There are certain Proxy properties that require extended templates in the PropertiesView panel. This templates are configured at the bottom section of the PropertiesView.xaml file. For example the TextBoxProxy will require an extended template to configure its bindings to the "Text" property:

XML
<WpfPropertyGrid:PropertyGrid.Editors>
	<WpfPropertyGrid:PropertyEditor DeclaringType="{x:Type WPFPropertyGridProxies:TextBoxProxy}" 
	  PropertyName="Text" InlineTemplate="{StaticResource TextProxyEditor_Inline}" 
	  ExtendedTemplate="{StaticResource TextProxyEditor_Extended}" />
</WpfPropertyGrid:PropertyGrid>  

Those templates are defined under the Resources\DataTemplates\PropertiesViewDataTemplates.xaml file.

It looks like this in the Properties View panel (Inline template and Extended template respectively):

Image 12 Image 13

For more information on Properties templates please refer to the WPF PropertyGrid project page.

Data Bindings

Data bindings for specific control properties like Text are configured mostly through the  Extended template shown above. 

New Bindings 

When a new control is dragged to the Design surface and its bindings are configured through the 'Set Bindings' button, then the OnSetBindings_Click method will be executed in the Resources\DataTemplates\PropertiesViewDataTemplates.cs  class.

C#
private void OnSetBindings_Click(object sender, RoutedEventArgs e) 

Here the specific binding information for each type of control will be set. Notice that when a new control is dragged its background will be red.

Image 14

This indicates that the control has no binding configured yet. As soon as a valid binding is entered, then a Behavior is used to set its background back to white:

C#
ControlHasBindingBehavior.SetHasBinding(chk, true);  

This attached property just indicates whether a certain control has binding or not. Triggers are used in the FormDesignerSurface.xaml to set the default background color:

XML
<Style TargetType="TextBox">
    <Style.Triggers>
        <Trigger Property="Behaviors:ControlHasBindingBehavior.HasBinding" Value="False">
            <Setter Property="Background" Value="Red"/>
        </Trigger>
    </Style.Triggers>
</Style> 

Existing Bindings  

The Converters\BindingInfoToBindingEditorControlsConverter IMultiValueConverter class  is referenced in the Resources\DataTemplates\PropertiesViewDataTemplates.xaml file. This converter is used to read the binding information from a previously created control and display it in the PropertyView grid.  

List View configuration 

Most of the basic toolbox controls in this application (e.g. TextBox, CheckBox, DatePicker, etc.) can be configured through the Properties window at a basic level, like changing display properties or even setting data bindings for specific properties. However, ItemsControl like  ListView needs to be handled in a special way because user should be able to modify its cell templates dynamically, like adding cell templates to the ListView, specifying what type of control we want to use for each cell template and also set the data binding for those.

First of all, let's look at the ListView configured PropertyGrid  Editors at the bottom of PropertiesView.xaml:

XML
<WpfPropertyGrid:PropertyGrid.Editors>
	<WpfPropertyGrid:PropertyEditor DeclaringType="{x:Type WPFPropertyGridProxies:ListViewProxy}" 
	  PropertyName="Columns" InlineTemplate="{StaticResource ListViewProxy_Columns_Inline}" 
	  ExtendedTemplate="{StaticResource ListViewProxy_Columns_Extended}" />
	<WpfPropertyGrid:PropertyEditor DeclaringType="{x:Type WPFPropertyGridProxies:ListViewProxy}" 
	  PropertyName="ItemsSource" InlineTemplate="{StaticResource ListViewProxy_Columns_Inline}" 
	  ExtendedTemplate="{StaticResource TextProxyEditor_Extended}" />
	<WpfPropertyGrid:PropertyEditor DeclaringType="{x:Type WPFPropertyGridProxies:ListViewProxy}" 
	  PropertyName="SelectedItem" InlineTemplate="{StaticResource ListViewProxy_Columns_Inline}" 
	  ExtendedTemplate="{StaticResource TextProxyEditor_Extended}" />
</WpfPropertyGrid:PropertyGrid.Editors> 

In the section above we can see that three extended editors have been configured for ListView control type:

Image 15

Columns Editor -   Here is where we can add cell templates, set Control Type for Column template and set bindings.

Image 16

Let's focus on the Columns extended  template shown above for a minute.

First we need to click the 'Add Column' button to create a new CellTemplate. At this point we can enter the name of the Column Header, Control Type and the data binding settings.

Notice that all this logic takes place in the WPFPropertyGridProxies\ListViewProxy.cs class, specifically in the OnAddColumn method:

C#
private void OnAddColumn(object obj)
{
    var pg = UIHelper.FindParent<PropertyGrid>(obj as FrameworkElement, false);
    if (pg == null) return;
    var lvp = pg.SelectedObject as ListViewProxy;
    if (lvp == null) return;
    var gv = lvp.Component.View as GridView;
    if (gv == null) return;

    //Create First Dummy Column
    var gvc = new GridViewColumn();
    gvc.Header = "New Column"; 

Once we're done configuring the new Cell template we click the 'Set Template' button which will call the following method in the same ListViewProxy.cs file to build the corresponding Template:

C#
private void OnSetColumnTemplateCommand(object obj)
{
    if (SelectedColumnTemplateControlType != null && 
          SelectedColumn != null && SelectedColumn.CellTemplate != null)
    {
        switch(SelectedColumnTemplateControlType.Name)
        {
            case "ComboBox":
                BuildComboBoxTemplate();
                break;
            case "TextBox":
                BuildTextBoxTemplate();
                break;
            case "TextBlock":
                BuildTextBlockTemplate();
                break;
            case "CheckBox":
                BuildCheckBoxTemplate();
                break;
            case "DatePicker":
                BuildDatePickerTemplate();
                break;
        }
    }
} 

Same procedure needs to be repeated depending on the number of Columns (Cell Templates) we want to configure. Columns can be re-configured by selecting a Column from the Column ComboBox on top of the Columns editor.

Image 17

Once all columns are configured then just click the 'Done' button.

Notice that there is a 'Show remove button' CheckBox right above the 'Done' button.  If this is enabled it will add a ListView cell template with a Remove button at the last column so end users can remove this row.

ItemsSource and SelectedItem extended editors can be used to configure the bindings to the source Collection in the backing ViewModel  and the SelectedItem property, respectively. 

Here is how the resulting XAML looks after creating a sample two column ListView (only showing part of the ListView definition):

XML
<ListView MinWidth="130" 
          ItemsSource="{x:Null}" 
          ItemTemplateSelector="{x:Null}" 
          SelectedItem="{x:Null}">
  <ListView.View>
    <GridView>
      <GridViewColumn DisplayMemberBinding="{x:Null}">
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <TextBox Width="100" Height="20" 
              Text="{Binding Path=Col1.Property, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
          </DataTemplate>
        </GridViewColumn.CellTemplate>My 1st Col</GridViewColumn>
      <GridViewColumn DisplayMemberBinding="{x:Null}">
        <GridViewColumn.CellTemplate>
          <DataTemplate>
            <CheckBox IsChecked="{Binding Path=Col2.Property, Mode=TwoWay, 
                            UpdateSourceTrigger=PropertyChanged}" />
          </DataTemplate>
        </GridViewColumn.CellTemplate>My 2nd Col</GridViewColumn>
      <GridViewColumn DisplayMemberBinding="{x:Null}">
    <GridViewColumn.CellTemplate>
      <DataTemplate>
        <Button Command="{Binding Path=DataContext.RemoveObservableCollectionItemCommand, 
          RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl, AncestorLevel=1}}" 
          CommandParameter="{Binding Path=ItemsSource, RelativeSource={RelativeSource Mode=FindAncestor, 
            AncestorType=ListView, AncestorLevel=1}}">Remove</Button>
      </DataTemplate>
    </GridViewColumn.CellTemplate>Remove</GridViewColumn>
</GridView>
</ListView.View>

Points of Interest / Limitations

I hope you find useful this alternative, which I believe has a lot of potential for improvement. Here is a list of things to consider and some limitations that can be worked out in the future:

  • The XAML generated by the app contains design-time UI elements that need to be removed in order
    to  be displayed to the user for data entry. This could be accomplished through an XSLT transform.
  • The  grid column definitions are fixed to 6 but this number should be configurable ideally
  •  Only the basic controls are included in the toolbox like DatePicker, Label, TextBox, Checkbox, Expander, ListView. More custom controls can be added
  • The 'Merge' option is very limited, can be enhanced in multiple ways

Wishlist 

Here is a list of things I'd like to incorporate to the WPF Form Designer in the future:

  • Multi-selection of controls to change properties of multiple controls at once 
  • Grouping of controls. Add a GroupBox to the toolbox to group controls  
  • Re-position of rows/columns. Be able to drag/ drop Grid rows or columns to move to different location
    like swapping or re-locating.  
  • Configurable number of columns 
  • Have a friendly way to merge cells (ColumnSpan), similar to Excel.

Hope you find it useful and I'll be more than happy to hear about suggestions, bugs found, enhancements, etc.

History

  • 3/31/2013 - Added documentation on how to configure a ListView control (structure + bindings)
  • 4/3/2013 - Added Wishlist section of potential future enhancements.
  • 4/3/2013 - Added Resizing documentation and acknowledgements for Denis Vuyka for Resizing Adorner and Alex Dov for Xaml Serialization  

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Sr. application developer currently developing desktop and web applications for the specialty insurance sector. Data integration specialist, interested in learning the latest .Net technologies.

Comments and Discussions

 
QuestionThere is any option to drag control without click on add row button Pin
Member 1534198026-Sep-21 19:01
Member 1534198026-Sep-21 19:01 
QuestionHow can i access a control in mvvm in view from model ? Pin
yasaminat28-Jul-15 20:56
yasaminat28-Jul-15 20:56 
QuestionHow to set binding on combobox? Pin
ivanaGoce13-May-15 2:48
ivanaGoce13-May-15 2:48 
Questionhelp full Pin
lalitraghav10-Dec-14 19:20
lalitraghav10-Dec-14 19:20 
AnswerRe: help full Pin
AdolfoPerez11-Dec-14 8:31
AdolfoPerez11-Dec-14 8:31 
QuestionCreate Grid Dynamic in this project. Pin
sinhat24-Nov-14 15:30
sinhat24-Nov-14 15:30 
AnswerRe: Create Grid Dynamic in this project. Pin
AdolfoPerez11-Dec-14 8:32
AdolfoPerez11-Dec-14 8:32 
Questionmy vote of 5 Pin
Shambhoo kumar26-Mar-14 1:41
professionalShambhoo kumar26-Mar-14 1:41 
AnswerRe: my vote of 5 Pin
AdolfoPerez2-Sep-14 5:35
AdolfoPerez2-Sep-14 5:35 
GeneralMy vote of 5 Pin
_Nizar19-Sep-13 3:22
_Nizar19-Sep-13 3:22 
GeneralRe: My vote of 5 Pin
AdolfoPerez27-Sep-13 7:14
AdolfoPerez27-Sep-13 7:14 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA12-Apr-13 18:58
professionalȘtefan-Mihai MOGA12-Apr-13 18:58 
GeneralRe: My vote of 5 Pin
AdolfoPerez13-Apr-13 1:12
AdolfoPerez13-Apr-13 1:12 
QuestionFile Not found Pin
Sujeet Bhujbal31-Mar-13 19:32
Sujeet Bhujbal31-Mar-13 19:32 
AnswerRe: File Not found Pin
AdolfoPerez1-Apr-13 1:52
AdolfoPerez1-Apr-13 1:52 
GeneralRe: File Not found Pin
AdolfoPerez1-Apr-13 3:16
AdolfoPerez1-Apr-13 3:16 
GeneralMy vote of 5 Pin
npdev1331-Mar-13 18:27
npdev1331-Mar-13 18:27 
GeneralRe: My vote of 5 Pin
AdolfoPerez1-Apr-13 15:00
AdolfoPerez1-Apr-13 15:00 
GeneralMy vote of 5 Pin
Prasad Khandekar31-Mar-13 5:43
professionalPrasad Khandekar31-Mar-13 5:43 
GeneralRe: My vote of 5 Pin
AdolfoPerez1-Apr-13 15:00
AdolfoPerez1-Apr-13 15:00 
GeneralMy vote of 5 Pin
Espen Harlinn30-Mar-13 2:57
professionalEspen Harlinn30-Mar-13 2:57 
GeneralRe: My vote of 5 Pin
AdolfoPerez30-Mar-13 14:42
AdolfoPerez30-Mar-13 14:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.