Click here to Skip to main content
Click here to Skip to main content

WPF CRUD Generator (Scaffolding)

, 4 Jun 2009
Rate this:
Please Sign up or sign in to vote.
Semi-automatic GUI generation from Domain Models.

Introduction

In almost all enterprise applications - no matter if business or production - a big part of the UI is made of screens that help manage the master data of the system. They allow the user to create, modify, or delete data, or to invoke related functionality. So, instead of developing more or less similar forms, why not take the underlying domain model, examine it, add some additional configuration information, and let an automatism create the UI for you? This is what the CRUD generator tries to achieve!

Let's Start with an Example

I made a small demo application which is included in the download. In the Backend project, I've defined a domain model for a typical production application, with some classes (Order, OrderState, Customer, Material, Equipment, and Lot) and an application context class which gives access to the model:

The context has lists of the classes Customer, Material, and Equipment. These classes I call top-level classes because there is no other class that "owns" these ones. The Order class is owned by Customer, and the Lot is owned by Order (I tried to visualize this semantic by the use of the yellow comment blocks).

What we would like to have is a UI that gives us the ability to do the following things:

  1. Navigate through the hierarchical model-instances,
  2. Create, Read (Display), Modify (Update), and Delete data (CRUD), and
  3. Invoke methods of our entities.

Using the CrudDisplay control and an appropriate configuration, the generated UI could look like this:

Crud_Orders_Small.png

The CrudDisplay control is divided into three sections:

  1. The navigation section is used for two things: we can see a breadcrumb that shows us how we navigated to the current entity class, and we have buttons to directly navigate to the configured top-level entity classes.
  2. The master view displays all the instances present for the currently selected entity class.
  3. The details view. Here we can see details of a selected entity, we can add, modify, or delete entities, and we can invoke the configured operations of the selected entity.

Just play a little bit with the demo to get a feeling of how it works, how the user can navigate through the hierarchy, modify data, etc.

Configuring the Framework

The CRUD generator works on a given domain model, but it is not fully automatic so that we can keep a certain flexibility. We have to configure it, and this is done in three ways:

Configuration using Metadata

We can use attributes for the properties of our entity classes. With their help, we can tell the framework how / if a property should be displayed in the master view or the details / edit view. Alternatively, instead of using attributes, there is also the possibility of using the method-based API if we have no control of our domain model (see the next point).

[CrudPropertyConfiguration(
    "Actual Quantity",
    IsEnabledInEdit = false,
    IsEnabledInNew = false)]
public double ActualQuantity
{
    get { return Lots.Sum(it => it.Quantity); }
}

Configuration by Code

There is a fluent API that can be used to:

  • register types,
  • define the navigation hierarchy (which is not directly coupled to the hierarchy of the domain model)
  • define which operations can be invoked (add, modify, delete, or custom methods).
// Setup the Customer class
var customer = crudDisplay.CrudManager
    .RegisterType<Customer>(() => context.Customers)
    .DefineAddOperation(
        (c) => context.Customers.Add(c),
        () => context.CreateInstance(() => new Customer()),
        (c) => { })
    .DefineRemoveOperation(
        (c) => Delete(
             "Really delete customer?",
            () => context.Customers.Remove(c)))
    .DefineEditOperation(
        (c) => c,
        (c) => CancelOrCommitEdit(c),
        (c) => CancelOrCommitEdit(c));

As we can see, there is a CrudManager as part of our CrudDisplay (which inherits from Control). We can access the CrudManager to setup the CrudDisplay.

The framework is configured by a fluent API. There is a set of methods like RegisterType, DefineXXXOperation etc. Most of these methods need one or more delegates which have to be provided. This makes the framework extremely flexibly, because the user of the framework can make use of these very fine grained hooks to provide the framework with the “missing” functionalities. Therefore, from the point of view of the framework, it’s absolutely the same if your domain model is coupled with a service, an O/R mapper, or whatever – just write some lines of code that does what needs to be done – and that’s it!

Take a look at "Demo\WpfGui\MasterData.cs" and you will see what is done to configure the example.

Configuration by Templating

Another hook that can be used to extend CrudDisplay is by using WPF ControlTemplates that give the master view another look for a certain entity class:

<crud:CrudDisplay x:Name="crudDisplay">
    <crud:CrudDisplay.MasterTemplateDefinitions>
        <crud:TemplateDefinition EntityType="{x:Type model:Equipment}">
            <crud:TemplateDefinition.Template>
                <ControlTemplate TargetType="{x:Type crud:MasterView}">
                    <ListBox x:Name="PART_EntityList">
                        <ListBox.ItemsPanel>
                            <ItemsPanelTemplate>
                                <WrapPanel Orientation="Horizontal" 
                                             IsItemsHost="True" />
                            </ItemsPanelTemplate>
                        </ListBox.ItemsPanel>
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <Grid Margin="5">
                                    <Rectangle x:Name="backgroundRect" Fill="LightGray" 
                                       Stroke="DarkGray" StrokeThickness="2" 
                                       RadiusX="5" RadiusY="5" />
                                    <StackPanel Orientation="Horizontal" Margin="5">
                                        <StackPanel Margin="3">
                                            <TextBlock FontWeight="Bold" Text="{Binding Name}" 
                                              Margin="2" HorizontalAlignment="Center" />
                                            <Border BorderBrush="Black" 
                                                    BorderThickness="0.5" />
                                            <StackPanel Orientation="Horizontal">
                                                <TextBlock Text="Current Level: " Margin="2" />
                                                <TextBlock Text="{Binding CurrentLevel}" 
                                                  FontWeight="Bold" Margin="2" />
                                            </StackPanel>
                                            <TextBlock Text="Warn Level" Margin="2" />
                                            <TextBox Text="{Binding WarnLevel}" Margin="2" />
                                            <TextBlock Text="Min Level" Margin="2" />
                                            <TextBox Text="{Binding ExceptionLevel}" 
                                                     Margin="2" />
                                        </StackPanel>
                                        <Border CornerRadius="3" BorderBrush="DarkGray" 
                                                Background="Gray" 
                                                BorderThickness="1" Margin="3">
                                            <Border.Resources>
                                                <converter:LevelConverter 
                                                  x:Key="levelConverter" />
                                            </Border.Resources>
                                            <Viewbox Stretch="Fill" Width="10">
                                                <StackPanel Orientation="Horizontal">
                                                    <Rectangle Fill="Red" 
                                                               Height="1" Width="0" />
                                                    <Rectangle Fill="Blue" Width="1" 
                                                               VerticalAlignment="Bottom">
                                                        <Rectangle.Height>
                                                          <MultiBinding 
                                                            Converter=
                                                             "{StaticResource levelConverter}">
                                                                <Binding Path="CurrentLevel" />
                                                                <Binding Path="Size" />
                                                          </MultiBinding>
                                                        </Rectangle.Height>
                                                    </Rectangle>
                                                </StackPanel>
                                            </Viewbox>
                                        </Border>
                                    </StackPanel>
                                </Grid>
                                <DataTemplate.Triggers>
                                    <DataTrigger Binding="{Binding IsWarnLevelReached}" 
                                              Value="True">
                                        <Setter TargetName="backgroundRect" 
                                          Property="Fill" Value="Yellow" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding IsExceptionLevelReached}" 
                                          Value="True">
                                        <Setter TargetName="backgroundRect" 
                                          Property="Fill" Value="Red" />
                                    </DataTrigger>
                                </DataTemplate.Triggers>
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </ControlTemplate>
            </crud:TemplateDefinition.Template>
        </crud:TemplateDefinition>
    </crud:CrudDisplay.MasterTemplateDefinitions>
</crud:CrudDisplay>

If you are familiar with the WPF templating mechanism, the code speaks for itself: you can provide TemplateDefinitions for a specific entity by using the EntityType property. Inside the template, use a ControlTemplate with TargetType = typeof(Technewlogic.Crud.Controls.MasterView), and the MasterView-Template needs a Selector with the name PART_EntityList. Inside the template, the DataContext is set to the current entity. That’s it!

CRUD_Euipments2_Small.png

How it works

I’ll give a brief explanation of how the CRUD generator basically works.

  • The fluent API is used to create an internal configuration model which is mainly defined by the MethodManager and DataSourceManager.
  • There are visual controls, mainly the panels, and – of course – the CrudDisplay control itself.
  • The navigation section keeps track of the – guess – navigation! It is triggered by the visual controls and holds the ViewModels. If there is a request to navigate to another entity, it switches the ViewModels.

Current State of Development

From my point of view, the CRUD generator is still a prototype which was important for me as a proof of concept. In any case, I consider it having a high potential either for prototyping an application, or for use as a part in a productive environment. There are still a lot of things that have to be done, and I have a lot of features in my mind that can be added. For example:

  • Different ways of navigation: If you want to create a new entity that is deep in the hierarchy, you have to navigate through the concrete entities and then add a new child. Instead, a “flat” navigation could be possible where you have more powerful ways of selecting references.
  • Different controls for navigation could be used, for example, a treeview, etc.
  • In the edit section: for certain property types, instead of using just a TextBox to enter data, more sophisticated controls could be used, e.g., DatePicker, etc. It could also be possible to provide a hook where the user can register custom controls for certain types.

History

  • 3rd June, 2009: Initial post.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

About the Author

Ronald Schlenker
Software Developer (Senior) www.technewlogic.de
Germany Germany
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinmemberNinjaCross26-Jan-11 3:27 
GeneralSource Code for Demo and Framework PinmemberNagarajanp3-Feb-10 18:45 
QuestionCan this be used against RIA for silverlight? PinmemberYash Ganthe15-Jul-09 3:43 
QuestionComparisons Pinmemberstixoffire8-Jun-09 9:30 
AnswerRe: Comparisons PinmemberRonald Schlenker11-Jun-09 2:32 
GeneralRe: Comparisons Pinmemberstixoffire11-Jun-09 11:25 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 4 Jun 2009
Article Copyright 2009 by Ronald Schlenker
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid