Click here to Skip to main content
Click here to Skip to main content
Go to top

Presentation Patterns for XAML based Applications

, 17 Sep 2013
Rate this:
Please Sign up or sign in to vote.
Design patterns on the presentation layer for WPF, Silverlight and Windows Phone applications.

XAML Patterns

Introduction

In the recent years, a variety of different patterns, like MVVM/MVC and Command, have established themselves for the development of user interface oriented applications and been put to good use. This article describes specialized design patterns, which can be of use in user interface development of XAML based applications.

By means of an example library, several application examples from everyday life will be used to demonstrate these design patterns and to give an idea, how these can be put into code for reusable components.

Aside from the benefits of increasing reuse, such design patterns help to reduce the complexity of a system. They support the design process for future development because they advocate the common understanding of the involved designers.

During implementation of the design patterns, a variety of aspects in regard to communications, performance, resource management and composite development for WPF/Silverlight/Windows Phone 7 had to be considered. The following areas will be addressed:

  • Actions and synchronous/asynchronous commands
  • Performance with Containers for objects bound to UI elements
    Improve ObservableCollection performance up to 1000%
  • Synchronous/asynchronous loading of data 
  • Loading of data on demand
    Extend the TreeView with lazy loading
  • Container views for navigation and editing 
  • Controlling the selection in a collection
    Track the ListBox, ComboBox and DataGrid selection
  • Item Model – a view model abstraction
  • Hierarchical management of objects of diverse types 
  • Editors for editing elements
    Enhance the TreeView and DataGrid with CRUD operations 

As mentioned before, the library presented in this article should be taken as one possible variant of how to implement the given design patterns on the presentation layer. To stay focused on the important aspects with this extensive subject, no additional tools and components have been used and only standard .NET framework items have been used. Although the library can be used as it is, an adaptation to the target domain is considered reasonable.

The design patterns can be viewed isolated and can be put to use in an existing architecture.

Action and Command

Action

An Action describes a simple and self-contained operation and leaves its implementation open. It serves as a contract between an Action Provider and an Action Consumer:

Action

The main characteristic of an Action is its concentration on an actual situation or an operation. Several actions can be combined into an Action Group by topic, although they usually don't have any other relationship with each other. The goal is to reduce dependencies to lower overall complexity. The Actions and the Action Groups can be viewed as building blocks. The combination of these will be determined by an Action Consumer, for example the view model.

The following examples demonstrate the usage of the Action pattern:

Action Examples

Because an Action is a neutral definition, standardized components can be developed which can then be reused for different applications. For the further course we will use a library with the following Action Groups and Actions:

Action Group Action
Data
  • Clearable
  • Loadable
  • Refreshable
  • Resettable
  • Selectable
  • Sortable
  • Comparer Sortable
  • Comparison Sortable
  • Updateable
Item
  • Item Create
  • Item Refresh
  • Item Edit
  • Item Delete
Media
  • Play
  • Pause
  • Resume
  • Stop
Navigation
  • Expandable
  • Collapsible

The implementation of an Action is done in the form of a simple interface:

// ------------------------------------------------------------------------
public interface IExpandable
{

  // ----------------------------------------------------------------------
  bool IsExpandable { get; }

  // ----------------------------------------------------------------------
  void Expand();

} // interface IExpandable

A TreeView will serve as an example of an Action Provider:

// ------------------------------------------------------------------------
public class MyTreeView : TreeView, IExpandable
{

  // ----------------------------------------------------------------------
  bool IExpandable.IsExpandable
  {
    get
    {
      MyTreeViewItem selectedItem = SelectedTreeViewItem;
      return selectedItem != null && !selectedItem.IsExpanded &&
        selectedItem.Items != null && selectedItem.Items.Count > 0;
    }
  } // IExpandable.IsExpandable

  // ----------------------------------------------------------------------
  void IExpandable.Expand()
  {
    MyTreeViewItem selectedItem = SelectedTreeViewItem;
    if ( selectedItem != null )
    {
      selectedItem.IsExpanded = true;
    }
  } // IExpandable.Expand

} // class MyTreeView

The implementation of an Action Consumers happens through a command, which will be described in the following chapter.

Command

The Command design pattern, introduced by the GoF, is present in the .NET framework through the interface ICommand. A command starts a sequence of steps which is usually invoked by the user through some user interface control. Commands can be provided by different elements:

  • Class utilities/command containers like for example System.Windows.Input.ApplicationCommands
  • View models
  • UI controls

The trigger of a Command is usually something like a button, which is connected to the command by a binding to the property Command. Often the availability of the command is visualized in the UI element through ICommand.CanExecute (enabling). The invocation of the command happens by a call to the method ICommand.Execute.

The following derivation extends ICommand by the following example aspects:

// ------------------------------------------------------------------------
public interface ICommand : System.Windows.Input.ICommand, INotifyPropertyChanged, IDisposable
{

  // ----------------------------------------------------------------------
  event EventHandler Started;

  // ----------------------------------------------------------------------
  event EventHandler Finished;

  // ----------------------------------------------------------------------
  string Name { get; }

  // ----------------------------------------------------------------------
  bool IsExecuting { get; }

  // ----------------------------------------------------------------------
  void Refresh( object parameter );

} // interface ICommand
Identification The command has a name.
Asynchronous execution Depending on technology and usage scenario, the command will execute an asynchronous operation. By extension of Execute and the execution status IsExecuting, the command provides a common base for synchronous and asynchronous execution environments.
By registering to the events Started and Ended, external observers can keep track of the execution status.
Status Availability of the command is signaled through IsAvailable. The variant implemented here ensures that the command is not available during execution. The method Refresh can be used to explicitly refresh the availability status.
State changes To communicate changes of values, the .NET interface INoftifyPropertyChanged is supported.
Resource management To ensure proper release of any held resources, the .NET interface IDisposable is implemented.

To simplify the handling of multiple commands (execution control, resource management, etc.), they can be registered in a ICommandCollection.

Action Command

The design pattern Action Command combines the patterns for Action and Command. In general, an Action Command should only cover a single Action. This helps to reduce interconnectedness and complexity. This limitation also leads to better reusability and is the basis for libraries with predefined commands such as for example the ApplicationCommands of the .NET framework.

The following example demonstrates the implementation of an Action Commands:

// ------------------------------------------------------------------------
public class ExpandCommand : Command
{

  // ----------------------------------------------------------------------
  public ExpandCommand( IExpandable expandable )
  {
    if ( expandable == null )
    {
      throw new ArgumentNullException( "expandable" );
    }
    this.expandable = expandable;
  } // ExpandCommand

  // ----------------------------------------------------------------------
  public IExpandable Expandable
  {
    get { return expandable; }
  } // Expandable

  // ----------------------------------------------------------------------
  protected override bool IsAvailable( object parameter )
  {
    return Expandable.IsExpandable;
  } // IsAvailable

  // ----------------------------------------------------------------------
  protected override bool DoExecute( object parameter )
  {
    Expandable.Expand();
    return true; // finished
  } // DoExecute

  // ----------------------------------------------------------------------
  // members
  private readonly IExpandable expandable;

} // class ExpandCommand

The article WPF Command-Pattern Applied describes more usage scenarios of the Command design pattern.

Collections

Item Collection

An Item Collection is a container which is specialized on UI elements with bindings. The declaration of Item Collection covers several aspects:

  • List functionality: ICollection<TItem>
  • Sorting: ISortable, IComparisonSortable<TItem> and IComparerSortable<TItem>
  • Messaging: INotifyCollectionChanged and INotifyPropertyChanged
  • Data updating: IUpdateable
  • Resource management: IDisposable

The following interface IItemCollection describes an Item Collection:

// ------------------------------------------------------------------------
public interface IItemCollection<TItem> : ICollection<TItem>,
  ISortable, IComparisonSortable<TItem>, IComparerSortable<TItem>,
  INotifyCollectionChanged, INotifyPropertyChanged, IUpdateable, IDisposable
{

  // ----------------------------------------------------------------------
  void AddAll( IEnumerable<TItem> items );

  // ----------------------------------------------------------------------
  void Replace( TItem oldItem, TItem newItem );

} // interface IItemCollection
Sorting The IItemCollection offers the capability to sort its elements by different means. This sorting is permanent and should not be confused with the one of the .NET ICollectionView, which is used for sorted display.
Resource management Implementation of the .NET interface IDisposable helps in releasing any held resources. During disposal of the list, all elements will be disposed which themselves support the IDisposable contract.
AddAll Adds all elements of an enumerable source.
Replace Replaces an element by another one.

Aside from the various helper methods, the primary benefit of using Item Collection lies in its support for the Updateable action, which helps to ensure greatly increased performance upon bulk operations.

Many UI elements (ListBox, DataGrid, TreeView etc.) are designed to handle a list of elements. The property ItemsSource binds the list to the UI element. If the content of the list changes at runtime, this will be reflected in the corresponding UI element. The mechanism for this is based on the interface INotifyCollectionChanged. The list, in this case the ObservableCollection, implements INotifyCollectionChanged and the UI element checks whether the data source implements this interface. Is that the case, the notifications of the list will be received and put to display. The sequence of events looks like this:

Observable Collection

To reduce the amount of communication between the container and the UI element, the Item Collection extends the standard list with an update mechanism which is described by the action Updateable. This will lead to the following sequence of events:

Item Collection

Through the methods BeginUpdate and EndUpdate (greetings to VisualBasic), the notifications sent to the UI elements will be coordinated manually. Between these points in time, any number of arbitrary operations can be performed on the container (Add, Replace, Sort, Clear ...), without the UI element being notified about them. Final updating of the user interface happens at the end of the update sequence, which leads to an optimization in regard to layouting and rendering.

// ----------------------------------------------------------------------
public void LoadCustomers()
{
  Customers.BeginUpdate();
  Customers.Clear();
  Customers.Add( new CustomerModel( "Joe", "Smith" ) );
  Customers.Add( new CustomerModel( "Mary", "Jones" ) );
  Customers.Add( new CustomerModel( "Lisa", "Black" ) );
  Customers.Add( new CustomerModel( "Peter", "Brown" ) );
  Customers.EndUpdate();
} // LoadCustomers

The following recommendations should be followed in regard to the usage of BeginUpdate/EndUpdate:

  • It makes most sense with bulk operations like loading, merging, etc.
  • It doesn't make sense when operating on single list elements with Add, Insert or Remove
  • The calls always have to be balanced, which should be considered in exception handling

Measurements have shown that - depending on usage scenario - the use of Item Collection can lead to a tremendous improvement in performance. However, a general statement about the performance gains cannot be given, as this depends on various factors:

  • The number and order of operations during the update sequence
  • The binding method: DependecyProperty or INotifiyPropertyChanged
  • The way how a UI element renders a binding value
  • The implementation of UI elements is different for WPF and Silverlight
  • The Silverlight runtime: in browser or Out-Of-Browser

To get a reliable statement about the achievable performance gains, an individual test run in the targeted usage scenario is required.

The following compilation of results shows some measurements of an Item Collection which is bound to a ListBox, where each list element has 2 bindings. The measurements of the example ListBox Binding show the improvements of the loading times for bindings to a DependencyProperty DP, as well as for bindings using the INotifyPropertyChanged INPC interface:

Load Item
Count
DP
[ms]
DP Update
[ms]
DP Update
vs
DP
INPC
[ms]
INPC Update
[ms]
INPC Update
vs
INPC
INPC
vs
DP
INPC Update
vs
DP Update
WPF
ItemCollection
1'000'000 16'966 8'370 203% 10'522 3'538 297% 161% 237%
WPF
CollectionView
100'000 17'395 2'180 798% 19'687 1'853 1063% 88% 118%
WPF
CollectionView
10'000 750 217 346% 1'066 182 584% 70% 119%
Silverlight 1'000'000 33'316 24'035 139% 13'962 8'523 164% 239% 282%
Silverlight
OOB
1'000'000 29'599 21'820 136% 12'928 7'342 176% 229% 297%
Windows
Phone 7
100'000 11'827 10'937 108% 6'419 5'382 119% 184% 203%
Test-Computer: ThinkPad T410s, i5M520, 4 GB RAM, 120 GB SSD HD, Win7-64, Windows-Index 4.4

The measurement results for the WPF ListBox show, that especially with an ICollectionView and large item numbers, a striking improvement of the performance can be achieved. The Silverlight ListBox also shows a significant improvement, whereas on Windows Phone 7 the optimization is hardy discernible.

The next table shows the results for the DataGrid Binding example, which measures the improvement of load times of an Item Collection which is bound to a DataGrid. Each element in the DataGrid has 5 bindings:

Load Item
Count
DP
[ms]
DP Update
[ms]
DP Update
vs
DP
INPC
[ms]
INPC Update
[ms]
INPC Update
vs
INPC
INPC
vs
DP
INPC Update
vs
DP Update
WPF 1'000'000 17'277 8'478 204% 11'099 3'523 315% 156% 241%
Silverlight 1'000 307 166 184% 254 156 163% 121% 107%
Silverlight 10'000 2'148 338 635% 1'940 177 1098% 111% 192%
Silverlight 100'000 202'375 1'851 10935% 185'641 484 38382% 109% 383%
Silverlight
OOB
1'000 213 125 171% 192 109 176% 111% 114%
Silverlight
OOB
10'000 1'690 260 650% 1'534 140 1093% 110% 185%
Silverlight
OOB
100'000 198'116 1'695 11688% 183'494 452 40566% 108% 375%

The WPF DataGrid shows a similar improvement as with the ListBox, independent of the number of items. With the Silverlight DataGrid however, the optimization stands in relation to the number of items. The more items are in the list, the bigger the performance gain.

In a practical situation, many more factors have a considerable influence on overall load times, like communication, services, reading data, etc. Preparation of the UI is however responsible for a substantial part of the overall processing time, which leads to a noticeable improvement of overall performance when this optimization is used.

Item Collection View

Many applications offer the possibility to select/focus an element of a list, which will then be used for further operations (editing, deleting, copying, etc.). Such cases are supported by the Item Collection View with the following functionality:

Item Collection View

In contrast to the .NET ICollectionView, which is designed on top of an IEnumerable list for navigation, filtering and grouping, the Item Collection View specializes on lists bound to UI, which support the INotifyCollectionChanged interface. The following implementation solely concentrates on navigation and doesn't support filtering and grouping:

// ------------------------------------------------------------------------
public interface IItemCollectionView<out TItemCollection, TItem> :
  IEnumerable<TItem>, INotifyCollectionChanged, INotifyPropertyChanged, IDisposable
    where TItemCollection : class, INotifyCollectionChanged, IList<TItem>
{

  // ----------------------------------------------------------------------
  event CurrentChangingEventHandler CurrentChanging;

  // ----------------------------------------------------------------------
  event EventHandler CurrentItemChanged;

  // ----------------------------------------------------------------------
  TItemCollection Items { get; }

  // ----------------------------------------------------------------------
  ViewSyncMode SyncMode { get;}

  // ----------------------------------------------------------------------
  TItem CurrentItem { get; set; }

  // ----------------------------------------------------------------------
  int CurrentPosition { get; }

  // ----------------------------------------------------------------------
  bool SyncCurrentToNewItem { get; set; }

  // ----------------------------------------------------------------------
  void EnsureCurrent();

  // ----------------------------------------------------------------------
  bool MoveCurrentToFirst();

  // ----------------------------------------------------------------------
  bool MoveCurrentToLast();

  // ----------------------------------------------------------------------
  bool MoveCurrentToNext();

  // ----------------------------------------------------------------------
  bool MoveCurrentToPosition( int position );

  // ----------------------------------------------------------------------
  bool MoveCurrentToPrevious();

} // interface IItemCollectionView

The property CurrentItem represents the pointer and can be changed manually or via binding. The following example synchronizes the selection of the DataGrid with the CurrentItem:

<DataGrid
  ItemsSource="{Binding View.Items}"
  SelectedItem="{Binding View.CurrentItem, Mode=TwoWay}">
</DataGrid>

The position of the pointer can be controlled with the various MoveCurrentToXxx operations.

Upon changes to the Item Collection, the pointer can be synchronized using EnsureCurrent. The switch SyncCurrentToNewItem (default=true) determines whether the CurrentItem pointer should be adjusted when a new element is inserted. Additionally, the view guarantees that the pointer retains its position upon modifications such as deletion of the current item. This prevents arbitrary jumping of the active element.

With automatic synchronization of the CurrentItem turned on, the property SyncMode can be used to control the mode of synchronization:

  • ViewSyncMode.Synchronous: Adjustment of CurrentItem happens synchronously
  • ViewSyncMode.Asynchronous: Adjustment of CurrentItem happens asynchronously

Asynchronous adjustment is necessary when Items and CurrentItem of the view are bound to the same UI element. In such cases the asynchronous adjustment ensures that the UI element will not reset the value of CurrentItem (Two-Way Binding) upon changes to the list.

Item Collection Selection

Some applications offer the possibility to select multiple elements and use them for further operations. The pattern Item Collection Selection offers, based on the action Selectable, the control of element selection:

// ------------------------------------------------------------------------
public interface ISelectable
{

  // ----------------------------------------------------------------------
  event SelectionChangedEventHandler SelectionChanged;

  // ----------------------------------------------------------------------
  IEnumerable SelectedItems { get; }

  // ----------------------------------------------------------------------
  void UpdateSelection( IEnumerable removedItems, IEnumerable addedItems );

} // interface ISelectable

Its declaration happens in the interface IItemCollectionSelection:

// ------------------------------------------------------------------------
public interface IItemCollectionSelection<out TItemCollection, TItem> : IEnumerable<TItem>,
  INotifyCollectionChanged, INotifyPropertyChanged, ISelectable
    where TItemCollection : class, INotifyCollectionChanged, ICollection<TItem>, new()
{

  // ----------------------------------------------------------------------
  new TItemCollection SelectedItems { get; }

} // interface IItemCollectionSelection

The example Selection demonstrates the usage of the Item Collection Selection pattern.

View Models

The following chapter introduces several patterns for representing view models:

View Models

Item Model

The Item Model design pattern is used for describing objects which are designed for representation in a UI. Communication between the UI elements and the Item Model happens through actions (see chapter on Commands) and bindings to properties. Bindings are used to display property values in the UI and to change them. The binding of a value happens either through a DependencyProperty DP or with the help of the interface INotifyPropertyChanged INPC.

Which variant makes more sense depends on the situation at hand. A general recommendation states that DP are primarily used with UI elements and INPC for data centered objects. Because the Item Model is a construct which should cover both categories, it is not always possible to state a clear choice. The following overview offers some help for deciding which is appropriate and lists several aspects with their respective advantages and disadvantages:

Aspect Statement DP INPC
Inheritance DP require to inherit from the base class DependencyObject which is not always desirable or achievable. The WPF variant of DependencyObject seals the methods Equals and GetHashCode which can lead to undesired restrictions in the derivations. - +
Read-Only Values Values of DP can always be modified through bindings. There are values however, which should only be changed by the item itself, like for example a loading state. In such cases INPC should be used. - +
Threading Because of the base class DependencyObject, DP should only be used from a single thread. - +
Serialization Serialization of the DP base class DependencyObject is not possible with the .NET serialization. This can be circumvented with the XamlReader and XamlWriter tools. - +
Trigger INPC offers more flexibility in the control of when execution happens as well as the possibility to combine the changes for multiple values. - +
Testing For testing, the restrictions of DependencyObject in regard to threading become relevant, which prevents automated testing. - +
Performance While Microsoft recommends DP to improve performance (MSDN), the measurements for this article have shown that INPC often leads to improved behavior (see chapter Item Collection). ? ?
Readability/
Simplicity
Is a matter of taste. Beginners usually find the DP pattern easier to understand. ? ?
Resources DP supports the assignment of a value from a resource. + -
Styling/
Templating
DP properties can be changed via styles (using setters). + -
Animation Only DP can be animated. + -
Metadata
Overriding
DP can be changed by derivations, for example for the assignment of a different style. + -

Sources: DP vs INPC, WPF-DP on MSDN, Silverlight-DP on MSDN

To be able to treat an Item Model in a standardized way, the differences between DP and INPC have to be hidden and the definition packed into a neutral structure. Interfaces are the standard choice for this and provide the basis for the abstract definition of the Item Model:

// ------------------------------------------------------------------------
public interface IItemModel : INotifyPropertyChanged, IDisposable
{

  // ----------------------------------------------------------------------
  TDisposable RegisterDisposable<TDisposable>( TDisposable item )
    where TDisposable : IDisposable;

  // ----------------------------------------------------------------------
  void UnregisterDisposable<TDisposable>( TDisposable disposable, bool dispose = true )
    where TDisposable : IDisposable;

} // interface IItemModel

The definition is based on the interface INotifyPropertyChanged because this can be used in both model variants (DP and INPC). To support proper release of resources, the IDisposable interface is supported. With the help of the utility method RegisterDisposable it is possible to register resources which should also be released when the Item Model is disposed.

For the support of both implementation variants, the base classes Dependency.ItemModel and Notifiable.ItemModel are provided.

The following derivation demonstrates the combination of the design patterns Item Model, Action and Command:

// ------------------------------------------------------------------------
public class MyModel : ItemModel, IRefreshable
{

  // ----------------------------------------------------------------------
  public MyModel()
  {
    refreshCommand = RegisterDisposable( new RefreshCommand( this ) );
  } // MyModel

  // ----------------------------------------------------------------------
  public ICommand RefreshCommand
  {
    get { return refreshCommand; }
  } // RefreshCommand
  
  // ----------------------------------------------------------------------
  void IRefreshable.Refresh()
  {
    // refresh data
  } // IRefreshable.Refresh

  // ----------------------------------------------------------------------
  // members
  private readonly RefreshCommand refreshCommand;

} // class MyModel

Data Item Model

The Data Item Model is a specialization of Data Model with the added capability to support the load operation:

// ------------------------------------------------------------------------
public interface IDataItemModel : IItemModel, ILoadable
{

  // ----------------------------------------------------------------------
  event EventHandler Loading;

  // ----------------------------------------------------------------------
  event EventHandler Loaded;

} // interface IDataItemModel

Data Item Model both supports the synchronous and the asynchronous loading of its data. Corresponding to the Item Model, both implementation variants Dependency.DataItemModel and Notifiable.DataItemModel are provided.

The following example demonstrates the synchronous loading of data:

// ------------------------------------------------------------------------
public class MyModel : DataItemModel
{

  // ----------------------------------------------------------------------
  protected override bool DoLoad()
  {
    // data load
    LoadDataSync();
    return true; // true = load finished -> synchronous
  } // DoLoad

} // class MyModel

The following example demonstrates the asynchronous loading of data:

// ------------------------------------------------------------------------
public class MyDataModel : DataItemModel
{

  // ----------------------------------------------------------------------
  protected override bool DoLoad()
  {
    // invoke data load
    LoadDataAsync( new Action( DataLoaded ) );
    return false; // false = continue loading -> asynchronous
  } // DoLoad

  // ----------------------------------------------------------------------
  private void DataLoaded()
  {
    // apply new data
    LoadFinished(); // indicate end of load
  } // DataLoaded

} // class MyDataModel

Hierarchical Item Model

For the representation of more complex data structures, hierarchical models are often used. Many applications use hierarchies to represent functional areas or documents.

The difficulty with the representation of hierarchical structures lies in the required harmonization of elements with differing representation and runtime behavior in one abstract structure. The following aspects can be valid for any set of elements in the hierarchy:

  • The element has properties which can be edited
  • The element can be deleted
  • The element can have child elements
  • The number of child elements is static, hence it is a pure structural element
  • The child elements have to be fetched from some data source
  • The child elements should only be (re-) loaded on demand
  • A child element can be added

The adaptation of these (relatively abstract) considerations can best be illustrated using some examples. The first one shows the objects of a .NET application in a hierarchical display:

Reflection Model

The following picture shows a possible hierarchical structure of a simple order management:

Order Model

The Hierarchical Item Model defines a pattern with which UI models can be managed in a hierarchical way. The elements in this model have all to be derived from the same Item Model. A mix with other elements is not supported. The Hierarchical Item Model is described by the interface IHierarchicalItemModel:

// ------------------------------------------------------------------------
public interface IHierarchicalItemModel<out TItemCollection, out TItem> : IItemModel
  where TItemCollection : class, ICollection<TItem>, new()
  where TItem : IItemModel, IHierarchicalItemModel<TItemCollection,TItem>
{

  // ----------------------------------------------------------------------
  bool HasChildren { get; }

  // ----------------------------------------------------------------------
  TItemCollection Children { get; }

  // ----------------------------------------------------------------------
  TItem Parent { get; }

  // ----------------------------------------------------------------------
  bool IsRoot { get; }

} // interface IHierarchicalItemModel

The definition governs the relationship between father and child elements. The root element in the hierarchy has no father element.

As with the Item Model there exist two implementation variants here as well: Dependency.HierarchicalItemModel and Notifiable.HierarchicalItemModel.

Hierarchical Data Item Model

The Hierarchical Data Item Model combines the patterns Data Item Model and Hierarchical Item Model and allows the management of hierarchical structures whose elements can be loaded dynamically:

// ------------------------------------------------------------------------
public interface IHierarchicalDataItemModel<TItem> : IDataItemModel, IHierarchicalItemModel<TItem>
  where TItem : IDataItemModel, IHierarchicalDataItemModel<TItem>
{
} // interface IHierarchicalDataItemModel

The usage of the Hierarchical Data Item Model will be described in more detail further on in the examples Assembly Browser and Order Browser.

The implementation of HierarchicalDataItemModel demonstrates how a class can be developed for generic usage. According to its declaration, the elements have to support the interface ILoadable (via IDataModel). To not lose the advantage of the update mechanism provided by Item Collection, the class checks upon loading whether the element supports IUpdateable. In such a case, the loading process additionally supports BeginUpdate and EndUpdate.

View Editors

Item Editor

The design pattern Item Editor represents the editing context of an element and offers corresponding operations. The Item Editor acts as a connecting coordinator between the selected element and the editing commands. The following implementation is focused on operations of an LOB object, what amounts to the standard CRUD operations in database terms:

// ------------------------------------------------------------------------
public interface IItemEditor : IItemCreate, IItemEdit, IItemDelete, IItemRefresh,
  INotifyPropertyChanged, IDisposable
{

  // ----------------------------------------------------------------------
  event EventHandler ItemChanged;

  // ----------------------------------------------------------------------
  object Item { get; set; }

  // ----------------------------------------------------------------------
  ItemCreateCommand ItemCreateCommand { get; }

  // ----------------------------------------------------------------------
  ItemEditCommand ItemEditCommand { get; }

  // ----------------------------------------------------------------------
  ItemDeleteCommand ItemDeleteCommand { get; }

  // ----------------------------------------------------------------------
  ItemRefreshCommand ItemRefreshCommand { get; }

} // interface IItemEditor

A note in regard to this design:
This implementation variant doesn't restrict its use in terms of the item and uses the type object. In closed usage scenarios, the Item Editor could be specialized on a certain type: IItemEditor<TItem> where TItem : IMyItemType.

The Item Editor provides the editing commands which will be bound to the corresponding controls in the view:

<Button
  Content="New"
  Command="{Binding Editor.ItemCreateCommand}" />
<Button
  Content="Edit"
  Command="{Binding Editor.ItemEditCommand}" />
<Button
  Content="Delete"
  Command="{Binding Editor.ItemDeleteCommand}" />
<Button
  Content="Refresh"
  Command="{Binding Editor.ItemRefreshCommand}" />

Which conditions have to be fulfilled for which corresponding actions is determined in a derivation of class ItemEditor:

// ------------------------------------------------------------------------
public class MyItemEditor : ItemEditor
{

  // ----------------------------------------------------------------------
  protected override void UpdateCommands()
  {
    CanCreate =
      Item is CompanyCollectionModel ||
      Item is CustomerCollectionModel ||
      Item is OrderCollectionModel;
    CanEdit =
      Item is CompanyModel ||
      Item is CustomerModel ||
      Item is OrderModel;
    CanDelete = CanEdit;
    CanRefresh = Item is IRefreshable;
  } // UpdateCommands

  // ----------------------------------------------------------------------
  public override void Create( Action onFinished )
  {
    // create operations
base.Create( onFinished );
  } // Create

  // ----------------------------------------------------------------------
  public override void Edit( Action onFinished )
  {
    // edit operations
base.Edit( onFinished );
  } // Edit

  // ----------------------------------------------------------------------
  public override void Delete( Action onFinished )
  {
    // delete operations
base.Delete( onFinished );
  } // Delete

} // class MyItemEditor

Item Collection Editor

The Item Collection Editor is a specialization of Item Editor and covers the editing of elements within an Item Collection. The integrated Item Collection View allows navigation of the associated Item Collection and thus allows for operations specific to the current position:

// ------------------------------------------------------------------------
public interface IItemCollectionEditor<TItemCollection, TItem> : IItemEditor
  where TItemCollection : class, INotifyCollectionChanged, IList<TItem>
{

  // ----------------------------------------------------------------------
  TItemCollection Items { get; set; }

  // ----------------------------------------------------------------------
  IItemCollectionView<TItemCollection, TItem> View { get; }

  // ----------------------------------------------------------------------
  TItem CurrentItem { get; set; }

  // ----------------------------------------------------------------------
  int CurrentPosition { get; }

  // ----------------------------------------------------------------------
  void EnsureItem();

} // interface IItemCollectionEditor

The example Customer Admin demonstrates the usage of the Item Collection Editor.

Item Editor Provider

The Item Editor Provider is an Item Editor which dynamically provides a matching editor for the current element. Based on the .NET object type Type it is possible to register an editor per type. Such editors based on the type of an element are most applicable in hierarchical structures where the element type changes dynamically through user interaction and an element type can occur at several locations within the hierarchy. The interface IItemEditorProvider represents an Item Editor Provider as follows:

// ------------------------------------------------------------------------
public interface IItemEditorProvider : IItemEditor
{

  // ----------------------------------------------------------------------
  void RegisterEditor( Type itemType, IItemEditor editor );

  // ----------------------------------------------------------------------
  void UnregisterEditor( Type itemType );

} // interface IItemEditorProvider

To use an ItemEditorProvider, an editor is registered for every type:

ItemEditorProvider editor = new ItemEditorProvider();
editor.RegisterEditor( typeof( MyModel ), new MyModelEditor() );

MyItemModelEditor itemEditor = new MyItemModelEditor();
editor.RegisterEditor( typeof( MyCollectionModel ), itemEditor );
editor.RegisterEditor( typeof( MyItemModel ), itemEditor );

The example Order Browser demonstrates usage of the Item Editor Provider.

View Presenters

Item Presenter

An Item Presenter describes some object which is responsible for displaying an element. In the UI, the Item Presenter makes certain that the selection of an element will display a corresponding UI element for its properties for example. Its representation is covered in the interface IItemPresenter:

// ------------------------------------------------------------------------
public interface IItemPresenter
{

  // ----------------------------------------------------------------------
  object BuildContent( object item );

} // interface IItemPresenter

Using the method BuildContent, the Item Presenter creates the UI content for the element.

View Item Presenter

The specialization View Item Presenter is used to determine an Item Presenter, which provides a view, or more precisely the FrameworkElement, for an element:

// ------------------------------------------------------------------------
public interface IViewItemPresenter<TView> : IItemPresenter
  where TView : FrameworkElement, new()
{
} // interface IViewItemPresenter

Item Presenter Provider

The Item Presenter Provider coordinates the available Item Presenters for an element according to the type:

// ------------------------------------------------------------------------
public interface IItemPresenterProvider : INotifyPropertyChanged, IDisposable
{

  // ----------------------------------------------------------------------
  object Item { get; set; }

  // ----------------------------------------------------------------------
  IEnumerable ItemContent { get; }

  // ----------------------------------------------------------------------
  void RegisterPresenter( Type itemType, IItemPresenter presenter );

  // ----------------------------------------------------------------------
  void UnregisterPresenter( Type itemType, IItemPresenter presenter );

} // interface IItemPresenterProvider

For each element type an IItemPresenter can be registered. Upon each change of the Item, the contents of the available Item Presenters will be made available in the property ItemContent. Through the property DefaultPresenter it is possible to select an Item Presenter for unknown types.

The example Order Browser demonstrates the usage of the Item Presenter Provider.

Examples

Media Player

By means of a media player it is demonstrated how the control MediaPlayerController builds a bridge between the existing controls MediaPlayer and Button to control interaction with Action and Command.

The MediaPlayerController controls a MediaPlayer and has no visual representation of its own. The property ElementName connects the MediaPlayerController with the MediaPlayer:

<MediaElement
  x:Name="MediaElement"
  Grid.Row="0"
  LoadedBehavior="Manual"
  Source="{Binding Source}" />

<XamlControls:MediaPlayerController
  x:Name="MediaPlayerController"
  MediaElement="{Binding ElementName=MediaElement}" />

The control of the MediaPlayer happens through buttons which are bound to the commands of the MediaPlayerControllers:

<Button
  Content="Play"
  ToolTipService.ToolTip="Play Movie"
  Visibility="{Binding CanPlay, ElementName=MediaPlayerController,
    Converter={StaticResource FlagToVisibleConverter}}"
  Command="{Binding PlayCommand, ElementName=MediaPlayerController}" />

The various commands of the media player Play/Pause/Resume/Stop are held in a CommandCollection and will be released upon disposal of the MediaPlayerController.

Further on, the MediaPlayerController demonstrates how to harmonize the discrepancies between the control versions of MediaPlayer for WPF and Silverlight/WP7.

Collection Binding

The examples ListBox Binding and GridView Binding demonstrate the binding of lists to the corresponding UI element. Their primary use is to measure loading performance (see chapter Item Collection). The following runtime aspects can be adapted:

  • The kind of the data source: IItemCollection or ICollectionView (only for WPF ListBox)
  • The type of binding to the Item Model: DependencyProperty or INotifyPropertyChanged
  • The number of items
  • The update mode – with or without support for IUpdateable

The example GridView Binding is only available for WPF and Silverlight.

Selection

The example Selection uses the controls ListBox and DataGrid to demonstrate how the pattern Item Collection Selection can be used to handle multiple selections.

Connection of the controls to the ItemCollectionSelection happens through the helper elements SelectorSelection (ListBox, ComboBox) as well as DataGridSelection (DataGrid):

<ListBox
  SelectionMode="Extended"
  ItemsSource="{Binding Customers}"
  Controls:SelectorSelection.Selection="{Binding CustomerSelection}" />

<DataGrid
  SelectionMode="Extended"
  ItemsSource="{Binding Customers}"
  Controls:DataGridSelection.Selection="{Binding CustomerSelection}" />

Customer Admin

The example Customer Admin demonstrates how the pattern Item Collection Editor can be applied to a DataGrid. It offers the following functionality:

  • Register a new customer
  • Determine the insert position of new customers
  • The selection behavior upon insertion of a new customer
  • Modify a customer
  • Delete a customer (with deletion confirmation)
  • Support for keyboard input (Insert, F2 and Delete)
  • Support for mouse double clicks

To properly support the desired functionality, the derivation ModelDataGrid of DataGrid was written. Aside from the Commands ItemCreate, ItemEdit and ItemDelete, ModelDataGrid provides the necessary support for keyboard input and handling of mouse clicks (tool MouseClickManager).

The editing functionality for customers is implemented in the class CustomerEditor. The CustomerAdminModel combines the information about a customer and the editor and acts as a view model for the view:

// ------------------------------------------------------------------------
public class CustomerAdminModel : ItemModel
{

  // ----------------------------------------------------------------------
  public CustomerAdminModel( ViewSyncMode syncMode )
  {
    RegisterDisposable( customers );
    editor = new CustomerAdminEditor( customers, syncMode );
    Load();
  } // CustomerAdminModel

  // ----------------------------------------------------------------------
  public ItemCollection<CustomerModel> Customers
  {
    get { return customers; }
  } // Customers

  // ----------------------------------------------------------------------
  public IItemEditor Editor
  {
    get { return editor; }
  } // Editor

  // ----------------------------------------------------------------------
  private void Load()
  {
  } // Load

  // ----------------------------------------------------------------------
  // members
  private readonly ItemCollection<CustomerModel> customers =
    new ItemCollection<CustomerModel>();
  private readonly CustomerAdminEditor editor;

} // class CustomerAdminModel

After the CustomerAdminModel has been assigned to the view as a DataContext, the view can make use of the elements as follows:

<Button
  Content="New"
  Command="{Binding Editor.ItemCreateCommand}" />

<XamlControls:ModelDataGrid
  ...
  IsSynchronizedWithCurrentItem="True"
  ItemsSource="{Binding Customers}"
  SelectedItem="{Binding Editor.Item, Mode=TwoWay}"
  ItemCreateCommand="{Binding Editor.ItemCreateCommand}" />
  ...
</XamlControls:ModelDataGrid>

Assembly Browser

The Assembly Browser lists the assemblies of the application in a hierarchy and uses the Hierarchical Data Item Model for this. The base class ReflectionModel with its various derivations acts as the basis for the hierarchical structure:

Assembly Browser

The class HierarchicalDataItemModel serves as base class for ReflectionModel, using a list declaration of ItemCollection<ReflectionModel>, which leads to the optimization of IUpdateable to be used for the loading of all lists.

The presentation of the assembly data happens in a TreeView. The TreeView control offers no support for on demand loading (lazy load) of its data and thus is made capable of this by the derivation ModeTreeView. Based on the action ILoadable, the classes ModeTreeView and ModelTreeViewItem offer the corresponding support. The required data will be loaded during construction of the TreeView elements (PrepareContainerForItemOverride). More information on this approach can be found in Silverlight TreeView Advanced Scenarios.

Additionally, the control ModeTreeView offers commands to expand and collapse a branch, which can be bound to any ICommand enabled control in the view:

<Button
  Content="Expand"
  Command="{Binding ExpandCommand, ElementName=AssembliesTree}" />
<Button
  Content="Collapse"
  Command="{Binding CollapseCommand, ElementName=AssembliesTree}" />

<XamlControls:ModelTreeView
  x:Name="AssembliesTree"
  ItemsSource="{Binding Assemblies.Children}" />

Order Browser

The Order Browser example is used to demonstrate how a hierarchical structure can be used to manage the following objects:

  • Management of companies
  • Management of customers of a company
  • Management of orders of a company
  • Display of order items of an order

To represent the view models, the pattern Hierarchical Data Item Model is used with the base class OrderModelBase:

Order Browser

Representation of a company is implemented in the class CompanyModel, which - aside from the company data - acts as a pure structure element for the customers and orders:

// ----------------------------------------------------------------------
protected override bool DoLoad()
{
  Children.Add( new CustomerCollectionModel( this ) );
  Children.Add( new OrderCollectionModel( this ) );
  return true;
} // DoLoad

The customers, represented by the class CustomerModel, will be loaded on demand from a data source by the container class CustomerCollectionModel:

// ----------------------------------------------------------------------
protected override bool DoLoad()
{
  ILIst<ICustomer> customers = LoadCustomers(); // load from your data source
  foreach ( ICustomer customer in customers )
  {
    Children.Add( new CustomerModel( this, customer.FirstName, customer.LastName, customer.Address ) );
  }
  return true;
} // DoLoad

The base class HierarchicalDataItemModel considers IUpdateable and executes the method DoLoad within BeginUpdate and EndUpdate.

Editing of elements is defined by the Item Editor Provider in the view model OrderBrowserModel of the view:

// ------------------------------------------------------------------------
public class OrderBrowserModel : OrderModelBase
{

  // ----------------------------------------------------------------------
  public OrderBrowserModel()
  {
    // companies
    companies = new CompanyCollectionModel( this );
    Children.Add( companies );

    // editor
    RegisterDisposable( editor );
    editor.ItemChanged += ItemChanged;
    editor.RegisterEditor( typeof( CompanyCollectionModel ), new CompanyCollectionEditor() );
    editor.RegisterEditor( typeof( CompanyModel ), new CompanyEditor() );

    CustomerEditor customerEditor = new CustomerEditor();
    editor.RegisterEditor( typeof( CustomerCollectionModel ), customerEditor );
    editor.RegisterEditor( typeof( CustomerModel ), customerEditor );

    OrderEditor orderEditor = new OrderEditor();
    editor.RegisterEditor( typeof( OrderCollectionModel ), orderEditor );
    editor.RegisterEditor( typeof( OrderModel ), orderEditor );

    // presenter
    RegisterDisposable( presenter );
    presenter.RegisterPresenter( typeof( CompanyModel ), new ViewItemPresenter<CompanyInfoView>() );
    presenter.RegisterPresenter( typeof( CustomerModel ), new ViewItemPresenter<CustomerInfoView>() );
    presenter.RegisterPresenter( typeof( OrderModel ), new ViewItemPresenter<OrderInfoView>() );
    presenter.RegisterPresenter( typeof( OrderModel ), new ViewItemPresenter<OrderItemsInfoView>() );
  } // OrderBrowserModel

  // ----------------------------------------------------------------------
  public override string Name
  {
    get { return "Companies"; }
  } // Name

  // ----------------------------------------------------------------------
  public CompanyCollectionModel Companies
  {
    get { return companies; }
  } // Customers

  // ----------------------------------------------------------------------
  public IItemEditor Editor
  {
    get { return editor; }
  } // Editor

  // ----------------------------------------------------------------------
  public IItemPresenterProvider Presenter
  {
    get { return presenter; }
  } // Presenter

  // ----------------------------------------------------------------------
  private void ItemChanged( object sender, EventArgs e )
  {
    presenter.Item = editor.Item;
  } // ItemChanged

  // ----------------------------------------------------------------------
  // members
  private readonly CompanyCollectionModel companies;
  private readonly ItemEditorProvider editor = new ItemEditorProvider();
  private readonly IItemPresenterProvider presenter = new ItemPresenterProvider();

} // class OrderBrowserModel

The editors registered in the ItemEditorProvider can be used for a single type or combined for multiple types. This is demonstrated in the example by the CustomerEditor which is used both for the types CustomerCollectionModel and CustomerModel.

To ensure that the functionality of the editors can be used in combination with the TreeView, the derivation ModelTreeView (also see example Assembly Browser) allows the binding to the Commands ItemCreate, ItemEdit and ItemDelete.

For the element types, corresponding Item Presenters are registered in the ItemPresenterProvider. The order demonstrates how several View Item Providers can be used for an element. Through ItemChanged, the active element of the ItemEditorProvider is synchronized with the ItemPresenterProvider.

Usage of the view happens as follows:

<Button
  Content="New"
  Command="{Binding Editor.ItemCreateCommand}" />
<Button
  Content="Edit"
  Command="{Binding Editor.ItemEditCommand}" />
<Button
  Content="Delete"
  Command="{Binding Editor.ItemDeleteCommand}" />
<Button
  Content="Refresh"
  Command="{Binding Editor.ItemRefreshCommand}" />

<XamlControls:ModelTreeView
  
  ItemsSource="{Binding Children}"
  ItemTemplate="{StaticResource NameTemplate}"
  SelectedItem="{Binding Editor.Item, Mode=TwoWay}"
  ItemCreateCommand="{Binding Editor.ItemCreateCommand}"
  ItemEditCommand="{Binding Editor.ItemEditCommand}"
  ItemDeleteCommand="{Binding Editor.ItemDeleteCommand}"/>

<ItemsControl
  ItemsSource="{Binding Presenter.ItemContent}"/>

Tools and Environment

Converter

For controlling dependencies between controls, the following base converters are used:

ObjectToVisibleConverter Changes the visibility of the target element to Visibility.Visible in case the source is not undefined.
ObjectToCollapsedConverter Changes the visibility of the target element to Visibility.Collapsedd in case the source is not undefined.
FlagToVisibleConverter Changes the visibility of the target element to Visibility.Visible in case the source is a Boolean with the value true.
FlagToCollapsedConverter Changes the visibility of the target element to Visibility.Collapsed in case the source is a Boolean with the value true.
UriToImageConverter Converts an Uri into a BitmapImage.

Composite Library Development

The article Time Period Library for .NET gives an in depth description of the development of Composite Libraries.

In composite development of Silverlight and Windows Phone 7, the debugger of the Windows Phone emulator doesn't consider the setting for the temporary build files. This can be corrected with the following Post-build event:

xcopy "$(ProjectDir)obj\WindowsPhone.$(ConfigurationName)\$(ConfigurationName)\XapCacheFile.xml" "$(ProjectDir)obj\$(ConfigurationName)\XapCacheFile.xml" /Y
  • 24th May, 2012 - v1.1.0.0
    • New utility PropertyTool
  • 14th May, 2012 - v1.0.0.0
    • Initial public release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jani Giannoudis
Software Developer (Senior)
Switzerland Switzerland
Jani is Co-founder of Meerazo.com, a free service which allows to share resources like locations, things, persons and their services in a cooperating group of people.

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermember6023-May-12 23:11 
AnswerRe: My vote of 5 PinmvpJani Giannoudis24-May-12 12:02 

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.140916.1 | Last Updated 18 Sep 2013
Article Copyright 2012 by Jani Giannoudis
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid