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

Item-Level Presentation Models for WPF

, 2 Apr 2011
Rate this:
Please Sign up or sign in to vote.
Make your life easier by inserting a Presentation Model layer (aka ViewModel) between your domain-model collection contents and template-generated WPF objects.

Introduction

As demonstrated in the article Simplifying the WPF TreeView by Using the ViewModel Pattern by Josh Smith, inserting a Presentation Model layer between your domain-model collection contents and template-generated WPF classes can make using WPF easier. Josh showed how to avoid writing procedural code against template-generated items such as instances of TreeViewItem. Attempting to do so causes headaches, making WPF seem more like a burden than a boon. WPF works fine when you're setting properties of template-generated items in XAML, but that ease of use is lost if you try directly doing so in the code-behind file. So save yourself the agony by taking an indirect approach.

In order to access and modify properties of template-generated items from the code-behind file, you first need to bind them to whatever is in the control's ItemsSource collection. Many of the properties you want to bind to (e.g., IsSelected and IsExpanded) will not be present on your domain objects, nor do you want to add them there since they are GUI-related rather than domain-related. The solution, as Josh presented in his article, is to create an additional Presentation Model layer consisting of objects that sit between your domain-model collection contents and the template-generated instances of WPF classes.

During the course of your adventures in WPF, it's likely you'll find other properties that are appropriate to the Presentation Model layer. These are properties that are clearly more GUI-related than domain-related, but would be quite convenient to have available as data-binding sources (without otherwise relying on value-converters). This article and the accompanying code library expand on this idea. The library makes it easy to create a new Presentation Model, of the sort just described, with minimal code. The ItemPresentationModel class provides a presentation-tailored representation of a domain-model type. And the HierarchicalPresentationModel class helps you by propagating modifications from a domain layer collection or tree structure, into a hierarchical Presentation Model.

Terminology

Two very similar design patterns have been described, each of which uses slightly different terminology. The original Presentation Model pattern by Martin Fowler uses the word "Presentation" to refer to the UI, whereas the latter WPF-specific Model-View-ViewModel by John Gossman uses "View". I prefer the original, and will use it throughout this article.

As for domain-specific Presentation Model classes, there is no universally accepted naming convention. The example in Fowler's work uses the combination of "Pmod" + domain class name, as in "PmodAlbum". Josh Smith's article started out using domain class name + "Presenter", but a reader pointed out that "it is somewhat of a misnomer". He therefore switched over to domain class name + "ViewModel", which follows John Gossman's terminology. For a while, I suffixed "View" onto Presentation Model classes, but this word's prominent use to refer to the UI layer has since led me to suffixing "PM" instead.

Presentation Model in WPF

This article isn't about the Presentation Model pattern per se. Fowler writes that "Presentation Model is not a GUI friendly facade to a specific domain object". However, the form of Presentation Model I use provides a data-binding friendly facade for each domain object to its corresponding template-generated item (e.g., TreeViewItem). This variation of the pattern is what I call "Item Presentation Model", and has a corresponding base class by the same name in the library attached to this article.

This ItemPresentationModel class wraps a domain object (the "Item") with a Presentation Model. The domain object is publicly accessible via the class's "DomainItem" property. This means you don't have to bother writing repetitive bridging properties of the form:

public string RegionName {
    get { return _region.RegionName; }
}

where no value is being added, since you have direct access to the wrapped domain item.

Flow of Changes

Displaying current data on all screens is generally required of an application. Neglecting this, as I've seen in other WPF-related articles on the web, can lead to problems as the codebase grows. For example, if Domain Model object dm1 has two corresponding Presentation Model objects pm1 and pm2, and if pm1 modifies dm1, then pm2 will contain stale data. The same problem can occur, for both pm1 and pm2, if some logic within the Domain Model modifies dm1. The solution is to make the Domain Model observable, so changes automatically flow to the Presentation Model.

Demo 1

This example demonstrates how easy it is to create a subclass of ItemPresentationModel. The code for the Demo1ItemPM Presentation Model class is as follows:

public class Demo1ItemPM: ItemPresentationModel<SampleItem> {
    public Demo1ItemPM ( SampleItem domainItem )
      : base( domainItem ) {
        UnsavedName = domainItem.Name;
    }

    private string unsavedName;

    public string UnsavedName {
        get { return unsavedName; }
        set { unsavedName = value; }
    }
}

Deriving from ItemPresentationModel gives you the DomainItem property mentioned above. You also get the IsSelected property from Josh's article, which gets data-bound in the XAML file like this:

<Style TargetType="{x:Type ListBoxItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>

UnsavedName is exactly the sort of property you would add to the Presentation Model, since it's not something that belongs in the domain-model, yet its use with data-binding makes things easier. Here's the text box for editing an item's name:

<TextBox Text="{Binding Path=UnsavedName, UpdateSourceTrigger=PropertyChanged}"
  Margin="-3,0,0,0"
  FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>

Thanks to the binding, the editableContentControl_ButtonClick() method can respond to saveButton and cancelButton clicks like this:

else if ( "saveButton".Equals( clickedButton.Name ) )
    targetItem.DomainItem.Name = targetItem.UnsavedName;
else if ( "cancelButton".Equals( clickedButton.Name ) )
    targetItem.UnsavedName = targetItem.DomainItem.Name;

If the saveButton is clicked, then the domain object's name is updated (after which you would persist the change to the database). If the cancelButton is clicked, then the UnsavedName value is reset.

Run the demo, and look at the source code of Demo1.xaml and Demo1.xaml.cs, for further details.

Demo 2

Now we move on to representing hierarchical domain-model structures, with a Presentation Model that interfaces with template-generated TreeViewItems. For this example, we have a SampleTreeNode domain class and its corresponding SampleTreeNodePM Presentation Model. The superclass of SampleTreeNodePM is my library's HierarchicalPresentationModel base class. It wraps individual items of the domain tree-structure, and has a recursive SubItems property that is used in the HierarchicalDataTemplate:

<HierarchicalDataTemplate DataType="{x:Type uimodel:SampleTreeNodePM}"
  ItemsSource="{Binding Path=SubItems}">
    <TextBlock Text="{Binding Path=DomainItem.Name}"/>
</HierarchicalDataTemplate>

When you instantiate a root object of this wrapper type (see code snippet below), it populates its tree with items from the domain tree-structure, first wrapping each one. You then bind this root object's SubItems property to TreeView.ItemSource (the following line in the snippet). But if the domain-model thereafter changes, how can the Presentation Model be updated to reflect those changes? For example, if the user removes a SampleTreeNode object's sub-item, how can that change be propagated to its corresponding SampleTreeNodePM? Luckily, my HierarchicalPresentationModel class takes care of that synchronization for you. After being created, each HierarchicalPresentationModel object listens to its corresponding domain collection's change events, adding/removing sub-items to/from itself as to stay in sync.

public partial class Demo2 : UserControl {
    private SampleTreeNodePM rootWrapper;
    
    public Demo2() {
        InitializeComponent();
        rootWrapper = new SampleTreeNodePM();
        sampleTreeView.ItemsSource = rootWrapper.SubItems;
    }

So a HierarchicalPresentationModel object acts as a sort of custom collection-view (analogous to WPF's CollectionView) over an original domain-model sub-items collection. The binding from a domain collection to its corresponding collection-view is one-way; that is, changes to the domain collection affect the collection-view but the reverse is not true. The binding from a collection-view to the ItemsSource property of its corresponding TreeView (if it's a root) or TreeViewItem (for all other nodes) is also one-way. In order to programmatically add/remove items, the client code modifies the domain collections; those changes are then propagated in the desired manner all the way to the TreeView or TreeViewItem.

Here's the code for the SampleTreeNodePM Presentation Model class:

public sealed class SampleTreeNodePM
  : HierarchicalPresentationModel<SampleTreeNode, SampleTreeNodePM> {

    /// <summary>Creates a root-level SampleTreeNodePM.</summary>
    public SampleTreeNodePM() : this( SampleTreeNode.Root ) { }

    /// <summary>
    /// Creates a SampleTreeNodePM that does not (yet) have any sub-items.
    /// </summary>
    /// <param name="domainItem">
    /// The SampleTreeNode to be represented by this node</param>
    private SampleTreeNodePM( SampleTreeNode domainItem )
      : base( domainItem, domainItem.SubNodes, true, true, true ) { }

    /// <inheritdoc/>
    protected override SampleTreeNodePM CreateInstance( SampleTreeNode domainItem ) {
        return new SampleTreeNodePM( domainItem );
    }

    /// <inheritdoc/>
    protected override HierarchicalPlacementArgs<SampleTreeNodePM>
      DesiredPosition( SampleTreeNode itemToAdd ) {
        return new HierarchicalPlacementArgs<SampleTreeNodePM>(
          OutliningCommands.NewChild, this, false );
    }
}

This subclass consists of four parts:

  1. A public constructor for creating the tree root.
  2. A private constructor that'll be used to create all other nodes.
  3. An override of the CreateInstance() method that returns an instance created via the latter constructor.
  4. An override of the DesiredPosition() method which, in this case, uses the simplest of implementations, i.e. add the new item to the sub-items collection. The third argument to the return value's constructor is false, indicating the resulting collection is not sorted.

As for the base constructor call in #2, you may be wondering what are the three boolean values being passed through:

  1. Specifies whether the Presentation Model subitems' indices are the same as in their corresponding source collection. We are mirroring the domain hierarchy exactly, so in this case the value is true.
  2. Specifies whether subitems also get deleted once their parent is deleted, which is also true in this case.
  3. Information useful for debugging DesiredPosition() implementations is written to the console when this value is true.

HierarchicalPresentationModel derives from ItemPresentationModel, so deriving from it gives you the same properties mentioned for Demo 1, as well as Josh's IsExpanded property which gets data-bound like this:

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>

Use SampleTreeNodePM as a template in your own projects. And check out the rest of the Demo2 code-behind file to see how to add, move, and remove nodes from the tree structure.

Demo 3

The HierarchicalPresentationModel class has another use, which is to provide a hierarchical representation of a flat collection. That is, given the input of a flat collection from the domain model, this class can help you produce a hierarchical Presentation Model ready to be data-bound to a TreeView. There are a couple of different ways to do this in WPF, but this is the only way I know to take a flat collection and display some items as children of others.

The original purpose of the HierarchicalPresentationModel code was, in fact, to display a flat collection as a hierarchy. This was my first use of the Presentation Model pattern, before I even knew what the Presentation Model pattern was! Adding the data-bound IsSelected and IsExpanded properties was an advantage I realized only later, after coming across Josh Smith's Simplifying the WPF TreeView... article.

The demo here uses the same domain data as Demo 1. It may be interesting to compare Demo 1 and 3 since they display the same data, only using a different Presentation Model.

The source code for the Demo3ItemPM class is as follows:

public sealed class Demo3ItemPM
  : HierarchicalPresentationModel<SampleItem,Demo3ItemPM> {

    /// <summary>Creates a root-level Demo3ItemPM.</summary>
    public Demo3ItemPM() : base( new Representative("Root"),
      App.SampleListContainer.SampleList, false, false, true ) { }

    /// <summary>
    /// Creates a Demo3ItemPM that does not (yet) have any sub-items.
    /// </summary>
    /// <param name="domainItem">The sample item to be represented by this node</param>
    private Demo3ItemPM( SampleItem domainItem )
      : base( domainItem, null, false, false, true ) { }

    /// <inheritdoc/>
    protected override Demo3ItemPM CreateInstance( SampleItem domainItem ) {
        return new Demo3ItemPM( domainItem );
    }

    /// <inheritdoc/>
    protected override HierarchicalPlacementArgs<Demo3ItemPM> DesiredPosition(
      SampleItem itemToAdd ) { ... }

    public Representative RepresentedBy {
        get {
            Represented represented = DomainItem as Represented;
            if ( represented != null ) return represented.RepresentedBy;
            else return null;
        }
        set {
            Represented represented = DomainItem as Represented;
            if ( represented != null ) represented.RepresentedBy = value;
        }
    }
}

This subclass consists of the same four parts as the one in Demo 2. You will notice a difference in the private constructor, however. In this case, a null value is passed in for the base constructor's subItemsSource parameter. This indicates that no sub-items will be recursively created by the base constructor, which is what we want in this case since the DesiredPosition() method will decide the subtree's arrangement. Following that, you'll notice the first two boolean values are also different from the previous demo: subitem indices do not correspond in this case, and deletes do not cascade.

The body of the DesiredPosition() method is omitted because it's a bit long. If you have a similar scenario, though, then you should take a look at it. Apart from the specific types mentioned (Represented and Representative), the logic may be applicable to your situation. After that comes the RepresentedBy bridging property, which I added since it made data-binding to the representedByComboBox so much easier:

<ComboBox Name="representedByComboBox" SelectedItem="{Binding RepresentedBy}"
  DisplayMemberPath="Name" Padding="5,2" Margin="0,0,10,10"/>

The problem was that two different subclasses are involved — one that has a RepresentedBy property, and another that doesn't. This bridging property prevents a binding error by just returning a null value when the RepresentedBy property isn't present.

License

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

About the Author

Adrian Alexander

United States United States
Adrian loves facilitating suave user experiences via the latest and greatest GUI technologies such as Windows 8 Metro-style apps as well as WPF. More generally, he finds joy in architecting software that is easy to comprehend and maintain. He does so by applying design patterns at the top-level, and by incessantly refactoring code at lower levels. He's always interested in hearing about opportunities for full or part-time development work. He resides in Pennsylvania but can potentially travel anywhere in the country. (Writing about himself in the third-person is Adrian's new hobby.)

Comments and Discussions

 
GeneralThank you. It is 5. Question... PinmemberMember 136401926-Nov-08 4:42 
GeneralThank you and question PinmemberMember 136401926-Nov-08 4:35 
I can describe my presantation model like "hierarchical" canvas.
Is it posible to define "hierarchical" canvas instead of TreeViewItem?
QuestionHow to handle NotifyPropertyChanged on wrapped object? Pinmembersbussinger26-Aug-08 11:38 
AnswerRe: How to handle NotifyPropertyChanged on wrapped object? PinmemberAdrian Alexander26-Aug-08 17:50 
GeneralIts a 4 from me for the same reason Josh stated PinmvpSacha Barber25-Aug-08 21:58 
GeneralRe: Its a 4 from me for the same reason Josh stated PinmemberAdrian Alexander25-Aug-08 23:28 
GeneralRe: Its a 4 from me for the same reason Josh stated PinmvpSacha Barber26-Aug-08 0:25 
GeneralRe: Its a 4 from me for the same reason Josh stated PinmemberAdrian Alexander26-Aug-08 0:40 
GeneralVery nice. PinmvpPete O'Hanlon24-Aug-08 11:16 
GeneralRe: Very nice. PinmemberAdrian Alexander25-Aug-08 3:02 
GeneralRe: Very nice. PinmvpJosh Smith25-Aug-08 9:04 
GeneralRe: Very nice. PinmemberAdrian Alexander29-Aug-08 0:45 
GeneralRe: Very nice. PinmvpJosh Smith29-Aug-08 2:09 

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
Web02 | 2.8.140721.1 | Last Updated 2 Apr 2011
Article Copyright 2008 by Adrian Alexander
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid