|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionAs 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 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 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 TerminologyTwo 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 WPFThis 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., This public string RegionName {
get { return _region.RegionName; }
}
where no value is being added, since you have direct access to the wrapped domain item. Demo 1This example demonstrates how easy it is to create a subclass of 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 <Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
<TextBox Text="{Binding Path=UnsavedName, UpdateSourceTrigger=PropertyChanged}"
Margin="-3,0,0,0"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"/>
Thanks to the binding, the else if ( "saveButton".Equals( clickedButton.Name ) )
targetItem.DomainItem.Name = targetItem.UnsavedName;
else if ( "cancelButton".Equals( clickedButton.Name ) )
targetItem.UnsavedName = targetItem.DomainItem.Name;
If the Run the demo, and look at the source code of Demo1.xaml and Demo1.xaml.cs, for further details. Demo 2Now we move on to representing hierarchical domain-model structures, with a Presentation-Model that interfaces with template-generated <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 public partial class Demo2 : UserControl {
private SampleTreeNodeView rootWrapper;
public Demo2() {
InitializeComponent();
rootWrapper = new SampleTreeNodeView();
sampleTreeView.ItemsSource = rootWrapper.SubItems;
}
So a Here's the code for the 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:
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
Use Demo 3The 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 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 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 I added a <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
|
||||||||||||||||||||||