5,695,118 members and growing! (15,723 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Presentation Foundation » General     Intermediate License: The Code Project Open License (CPOL)

Item Presentation Models for WPF

By Adrian Alexander

Make your life easier by inserting a Presentation Model layer between your domain-model collection contents and template-generated WPF objects.
C# (C# 2.0, C#), XML, Windows (Windows, WinXP, Vista), .NET (.NET, .NET 3.0, .NET 3.5), Visual Studio (VS2005, VS2008, Visual Studio), XAML, WPF, Architect, Dev

Posted: 24 Aug 2008
Updated: 3 Sep 2008
Views: 8,576
Bookmarked: 28 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
12 votes for this Article.
Popularity: 4.73 Rating: 4.38 out of 5
1 vote, 8.3%
1
0 votes, 0.0%
2
1 vote, 8.3%
3
5 votes, 41.7%
4
5 votes, 41.7%
5

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 your life easier. Josh showed how to avoid (attempting to) programmatically interact directly with template-generated items such as TreeViewItem. Doing so is a huge headache; it makes WPF seem more like a burden than a boon. WPF works fine when you're setting properties of template-generated items in XAML, but it will make your life hell if you try directly doing so programmatically. So, save yourself the agony by trying a different (indirect) approach.

In order to programmatically interact with properties of the template-generated items, you will 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 has already been presented in Josh's article, is to create an additional Presentation Model layer consisting of objects that sit between your domain-model collection contents and the template-generated WPF classes.

During the course of your adventures in WPF, it's likely you will 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 if available for data-binding from a template-generated WPF object. 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 user pointed out that "it is somewhat of a misnomer", so he switched to using domain class name + "ViewModel", which follows John Gossman's terminology. I, instead, use the rather unconventional suffix "View". I am aware that this naming breaks away from the terminology of MVC and its derivatives; therefore, I devote a blog entry to digressing my reasons.

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 façade 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 publically accessible via the class' "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.

Demo 1

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

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

    private string unsavedName;

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

Deriving from ItemPresentationModel gives you the DomainItem property mentioned above, as well as 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 SampleTreeNodeView Presentation-Model. The superclass of SampleTreeNodeView 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:SampleTreeNodeView}"
  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 SampleTreeNodeView? Luckily, my HierarchicalPresentationModel class takes care of that synchronisation 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 SampleTreeNodeView rootWrapper;
    
    public Demo2() {
        InitializeComponent();
        rootWrapper = new SampleTreeNodeView();
        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 SampleTreeNodeView Presentation-Model class:

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

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

    /// <summary>

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

    /// <inheritdoc/>

    protected override SampleTreeNodeView CreateInstance( SampleTreeNode domainItem ) {
        return new SampleTreeNodeView( domainItem );
    }

    /// <inheritdoc/>
    protected override void AddSubItem( SampleTreeNodeView itemToAdd, int atPosition ) {
        subItems.Insert( atPosition, itemToAdd );
    }
}

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 AddSubItem() method which, in this case, uses the simplest of implementations, i.e. add the new item to the sub-items collection

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 SampleTreeNodeView 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 solution I know of that will work when all you want to do is take a flat collection and display some items as children of others.

This was actually my first use of the Presentation Model pattern, before I even knew what the Presentation Model pattern was. The original purpose of the HierarchicalPresentationModel code was to display a flat collection as a hierarchy. Adding the data-bound IsSelected and IsExpanded properties was an advantage I realised 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 Demo3ItemView class is as follows:

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

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

    /// <summary>

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

    /// <inheritdoc/>

    protected override Demo3ItemView CreateInstance( SampleItem domainItem ) {
        return new Demo3ItemView( domainItem );
    }

    protected override void AddSubItem( Demo3ItemView itemToAdd,
        int atPosition ) { ... }

    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 AddSubItem() method will decide the subtree's arrangement. The body of the AddSubItem() method is omitted because it's a bit long. If you have a similiar 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.

I added a RepresentedBy bridging property 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 Code Project Open License (CPOL)

About the Author

Adrian Alexander


A laptop-toting nomad currently residing in a small town in the mountains of northern Thailand.
Location: Thailand Thailand

Other popular Windows Presentation Foundation articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 13 of 13 (Total in Forum: 13) (Refresh)FirstPrevNext
GeneralThank you. It is 5. Question...memberMember 13640195:42 26 Nov '08  
GeneralThank you and questionmemberMember 13640195:35 26 Nov '08  
GeneralHow to handle NotifyPropertyChanged on wrapped object?membersbussinger12:38 26 Aug '08  
GeneralRe: How to handle NotifyPropertyChanged on wrapped object?memberAdrian Alexander18:50 26 Aug '08  
GeneralIts a 4 from me for the same reason Josh statedmvpSacha Barber22:58 25 Aug '08  
GeneralRe: Its a 4 from me for the same reason Josh statedmemberAdrian Alexander0:28 26 Aug '08  
GeneralRe: Its a 4 from me for the same reason Josh statedmvpSacha Barber1:25 26 Aug '08  
GeneralRe: Its a 4 from me for the same reason Josh statedmemberAdrian Alexander1:40 26 Aug '08  
GeneralVery nice.mvpPete O'Hanlon12:16 24 Aug '08  
GeneralRe: Very nice.memberAdrian Alexander4:02 25 Aug '08  
GeneralRe: Very nice.mvpJosh Smith10:04 25 Aug '08  
GeneralRe: Very nice.memberAdrian Alexander1:45 29 Aug '08  
GeneralRe: Very nice.mvpJosh Smith3:09 29 Aug '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 3 Sep 2008
Editor: Sean Ewington
Copyright 2008 by Adrian Alexander
Everything else Copyright © CodeProject, 1999-2008
Web20 | Advertise on the Code Project