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:
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:
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
|
|
Navigation
|
|
The implementation of an Action is done in the form of a simple interface:
public interface IExpandable
{
bool IsExpandable { get; }
void Expand();
}
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;
}
}
void IExpandable.Expand()
{
MyTreeViewItem selectedItem = SelectedTreeViewItem;
if ( selectedItem != null )
{
selectedItem.IsExpanded = true;
}
}
}
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 );
}
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;
}
public IExpandable Expandable
{
get { return expandable; }
}
protected override bool IsAvailable( object parameter )
{
return Expandable.IsExpandable;
}
protected override bool DoExecute( object parameter )
{
Expandable.Expand();
return true;
}
private readonly IExpandable expandable;
}
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 );
}
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:
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:
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();
}
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:
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();
}
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 );
}
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; }
}
The example Selection demonstrates the usage of the Item Collection Selection pattern.
View Models
The following chapter introduces several patterns for representing 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;
}
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 ) );
}
public ICommand RefreshCommand
{
get { return refreshCommand; }
}
void IRefreshable.Refresh()
{
}
private readonly RefreshCommand refreshCommand;
}
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;
}
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()
{
LoadDataSync();
return true;
}
}
The following example demonstrates the asynchronous loading of data:
public class MyDataModel : DataItemModel
{
protected override bool DoLoad()
{
LoadDataAsync( new Action( DataLoaded ) );
return false;
}
private void DataLoaded()
{
LoadFinished();
}
}
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:
The following picture shows a possible hierarchical structure of a simple order management:
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; }
}
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>
{
}
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; }
}
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;
}
public override void Create( Action onFinished )
{
…
base.Create( onFinished );
}
public override void Edit( Action onFinished )
{
…
base.Edit( onFinished );
}
public override void Delete( Action onFinished )
{
…
base.Delete( onFinished );
}
}
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();
}
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 );
}
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 );
}
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()
{
}
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 );
}
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();
}
public ItemCollection<CustomerModel> Customers
{
get { return customers; }
}
public IItemEditor Editor
{
get { return editor; }
}
private void Load()
{
}
private readonly ItemCollection<CustomerModel> customers =
new ItemCollection<CustomerModel>();
private readonly CustomerAdminEditor editor;
}
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:
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
:
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;
}
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();
foreach ( ICustomer customer in customers )
{
Children.Add( new CustomerModel( this, customer.FirstName, customer.LastName, customer.Address ) );
}
return true;
}
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 = new CompanyCollectionModel( this );
Children.Add( companies );
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 );
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>() );
}
public override string Name
{
get { return "Companies"; }
}
public CompanyCollectionModel Companies
{
get { return companies; }
}
public IItemEditor Editor
{
get { return editor; }
}
public IItemPresenterProvider Presenter
{
get { return presenter; }
}
private void ItemChanged( object sender, EventArgs e )
{
presenter.Item = editor.Item;
}
private readonly CompanyCollectionModel companies;
private readonly ItemEditorProvider editor = new ItemEditorProvider();
private readonly IItemPresenterProvider presenter = new ItemPresenterProvider();
}
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.Collapsed d 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
- 14th May, 2012 - v1.0.0.0