WPF Application Design and Architecture

EDIT (12/6/2014): If you like C#, WPF, 3D, and Golf, check out my latest side project: https://www.youtube.com/watch?v=8pWq8vhZqbg
If you don’t like golf, don’t click the link!

As I have been studying WPF in the past several months, I have come to at least one conclusion: The power and flexibility of WPF comes with a subtle cost. This cost is that there are 100x more ways to design an application in WPF than there is in WinForms; therefore there are 100x more ways to shoot yourself in the foot. Fortunately, once one goes through the failures and learning cycle of WPF, it becomes much easier to design flexible and scalable applications in WPF than doing so in WinForms.

The purpose of this post is to illustrate the various ways a feature or WPF application can be designed.

This post:
-Provides a peek into the vast world of WPF application designs.
-Provides sample code for each design
-Is for beginner/intermediate level WPF application developers.
-Is not a ‘best practices’ guide, it just provides a set of examples to help readers understand how classes and interaction between objects can be designed.
-Is not a ‘How-To.’ The examples are extremely contrived and few WPF features are demonstrated.

Before continuing I suggest reading the following articles:
-Dr. WPF’s A Project Needs Structure
-Introduction to Model/View/ViewModel
-100 Model/View/ViewModels of Mt. Fuji
-Model-View-ViewModel pattern example
-ViewModel example
-Advantages and disadvantages of M-V-VM
-UML diagram of M-V-VM pattern
-WPF Patterns
-Dan Crevier’s DataModel-View-ViewModel Series
-DataModel and VIewModel

I also recommend studying the source and design of the following applications:
-Family.Show
-SceReader

If all of these concepts are new to you, it can be a bit overwhelming. It is a lot of information and concepts to absorb. Hopefully the samples provided in this post can make it easier to understand how these concepts can come into play in a real-world application. Four designs will be demonstrated, starting from an extremely simple design all the way to one that uses DataModel-View-ViewModel. Each sample has the same features to make it easier to follow along the evolution of the design.

The features that are demonstrated are as follows:
-Two buttons allow a user to find currently loaded assemblies and types
-A textbox allows the search to be filtered.
-The results are displayed in a listbox

Download the samples: WPFDesigns
I recommend browsing the source and following along as each sample and design is explained.

Design #1

For such a simple application, it really does not take much to implement the solution using just a window with some buttons and a ListBox on it. When a button is clicked, a handler can call a method and update the listbox’s ItemsSource directly.

public void ShowTypes_Click(object sender, RoutedEventArgs e)
{
this.ResultsListBox.ItemsSource = FindTypeNames();
}

1. A button is clicked and handled in Window1’s code behind.
2. The loaded types are retrieved.
3. The ListBox’s ItemsSource is updated

Design #2

In this sample the buttons and textbox are moved onto their own user control, while the ListBox is moved to its own user control as well. Breaking up a UI into modular components allows the UI to remain simple as many features are added, and forces the logic handling to be more modular as well.

<UserControl x:Class=”WpfDesigns2.Controls.OperationsUserControl”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
DataContext=”{Binding RelativeSource={RelativeSource Self}}”>
<Grid>
<StackPanel>
<Button Click=”ShowAssemblies_Click”>Show Assemblies</Button>
<Button Click=”ShowTypes_Click”>Show Types</Button>
<TextBlock>Match:</TextBlock>
<TextBox Width=”80″ Text=”{Binding Path=MatchText}”/>
</StackPanel>
</Grid>
</UserControl>

Now that each user control is handling its own events there needs to be some way to get the results from one user control to the other. Since the Window1 is hosting both user controls, in this sample we will make it listen to an operation complete event that contains the results, and then send those results to the user control with the ListBox so it can be updated.

1. The button click is handled in the Operations user control.
2. Using the MatchText variable and a ReflectionHelper object that exists in the Operations user control, the assemblies or types are retrieved.
3. The user control raises an event letting listeners know the results are complete.
4. Window1 handles the event and sends the results to the ReflectionResults property of the user control with the ListBox.
5. Since the ListBox is bound to the ReflectionResults dependency property, the ListBox UI is updated automatically.

Design #3

This sample takes the design a step further and breaks the UI from the logic. You can think of the user controls as the View and the logic as the Model. A manager called ApplicationManager was designed to contain all of the objects in the model. This manager is created in Window1’s XAML:

<ObjectDataProvider x:Key=”ApplicationManager” ObjectType=”{x:Type local:ApplicationManager}” />

Since Window1 contains the manager in its resources collection, it can pass on the manager’s child models to each respective user control:

<controls:OperationsUserControl
Grid.Column=”0″
x:Name=”OperationsControl”
DataContext=”{Binding Source={StaticResource ApplicationManager}, Path=ReflectionHelper}”/>
<controls:ReflectionResultsUserControl
Grid.Column=”1″
x:Name=”ReflectionDisplayControl”
DataContext=”{Binding Source={StaticResource ApplicationManager}, Path=ReflectionResults}”/>

Now each user control’s DataContext is set to an object from the Model layer, so that the UI elements can bind to a single model element. When the user control needs to get its model object, it can do so in the following way:

private ReflectionHelper ReflectionHelper
{
get
{
return this.DataContext as ReflectionHelper;
}
}

A button click in the handler is as easy as:

public void ShowTypes_Click(object sender, RoutedEventArgs e)
{
this.ReflectionHelper.FindTypes();
}

In this sample the ApplicationManager is handling listening for the results from one model and passing the data on to the results model. This separates the logic from the views. Since the ReflectionResultsUserControl is bound to the Results property on the results model, it gets updated automatically when the ApplicationManager updates the results model. At this stage the UserControls are doing virtually nothing and the model layer is doing all of the work (which is ideal).

1. The button click is handled in the OperationsUserControl.
2. The FindTypes or FindAssemblies method is called on the user control’s model element.
3. The reflection helper model element does some reflection work
4. When the work is complete an event is raised
5. The ApplicationManager listening for the event updates the ReflectionResults model element.
6. Since the ReflectionResultsUserControl’s ListBox is bound to the Results property of the ReflectionResults model element, the ListBox is updated automatically.

Design #4

At this point we have probably taken the samples as far as they need to go. However, to demonstrate one way the DataModel-View-ViewModel pattern can be implemented, sample #4 takes the design to the next level. Since the other DataModel-View-ViewModel articles already do a great job explaining the concepts, I will just describe the design for sample #4. Let’s start with the diagram:

1. Using a RoutedCommand the OperationsView handles the button click command.
2. The view calls into its view model to perform an operation.
3. Since the OperationsViewModel was storing the MatchText data, it can call into its model (ReflectionHelper) to find types or assemblies.
4. The ReflectionHelper model retrieves the matching types or assemblies.
5. The ReflectionHelper raises its operations complete event
6. The ApplicationManager handles the event and updates the ReflectionResults model.
7. The ReflectionResults model raises a results changed event
8. The ReflectionResultsVewModel handles the event and puts the results data into a format suitable for its view.
9. Since the ListBox in the ReflectionResultsView is bound to the ObservableCollection in its respective view model, the ListBox is updated automatically.

For such a small application this design is overkill. However, as applications grow in size and complexity, the DataModel-View-ViewModel pattern keeps the overall design simple and minimizes dependencies. It also has the added benefit of making all of the various modules much easier to test, which is obviously crucial to the success of any large piece of software.

One interesting aspect of the DM-V-VM is how well the view can be abstracted from the rest of the application. For example, in this sample a new class call ApplicationViewState was created to hold the view models that represent the views. A DataTemplate allows us to represent the view model in any way we want:

<DataTemplate DataType=”{x:Type viewModels:OperationsViewModel}”>
<!–Sets the view model as the data context–>
<views:OperationsView DataContext=”{Binding}” />
</DataTemplate>

In the main window a ContentPresenter is used to represent a ‘place holder’ for its content which is specified to be one of the view models:

<ContentPresenter
Grid.Column=”0″
Content=”{Binding Source={StaticResource ApplicationManager}, Path=ViewState.LeftSideView}”/>
<ContentPresenter
Grid.Column=”1″
Content=”{Binding Source={StaticResource ApplicationManager}, Path=ViewState.RightSideView}”/>

By swapping the view models in the ApplicationViewState, the views automatically follow their respective view model. This is a crude demonstration of a data driven UI.

Disclaimer: Design #4’s sample is not a perfect implementation of the DM-V-VM pattern. I have seen it implemented in various different ways. It usually depends on the requirements of the particular application. As far as I can tell there is also no difference between what people call a DataModel and a Model. Since many WPF applications work with a data layer of some sort such as a database, they call it the DataModel. In this last sample I could have easily just called the ReflectionHelper and ReflectionResults Models (I think I did a few times anyways).

A Word of Warning: If you are new to WPF, attempting to implement DM-V-VM for your application is extremely difficult. There are many ‘gotchas’ along the way that put major roadblocks in the way of solving problems. Most of these are WPF specific. Your best source of help is the MSDN WPF Forum.

In the real world, applications are usually a mix of all four of these designs. No single design fits every application. It is up to you to learn from your mistakes in order to learn how to design and develop simple, flexible, scalable applications. :) No matter how experienced you get, there is always more to learn!

Download the samples: WPFDesigns

All questions, comments, and criticism are welcome!

Comments are closed.