Introduction
Composite UI is one of the great patterns in the .NET framework that targets the large enterprise
UI centric applications. The pattern makes it possible to break a very complex
and heavy user interfaces into separate loosely coupled modules. The modules
can be developed by different teams and the pattern insures the loosely coupling
among the different parts such as modules, services and the main shell. From
an enterprise point of view, the pattern is very useful, but to be honest with
you, if you want to create an application in a RAD manner, you will encounter
some difficulties in the composite UI pattern. There are lots of issues that
slow down your hand in the pattern. For example, consider a simple application
that shows a list of data and let users to do the CRUD (Create, Read, Update
and Delete) operations. If you develop such application following the Composite
UI standards, then you should create two presenters, two views, one CompositeEvent and one module at least. Using the pattern has many benefits, but it involves sacrificing
the rapid application development. In this paper, I have tried to target the
gap between Composite UI pattern and Rapid Application Development for common
data entry scenarios. In other words, I will provide a library of classes that
makes the process of creating data entry forms in the Composite UI easier.
Any application that deals with data has some kind of data entry forms. In most cases,
the application shows a list of records for each type of data and the users can
do the CRUD operations such as editing, deleting, adding, searching and etc on
the data. For most of the applications, the data entry process contains the following
UI components.
- The simple list components that show a list of entities to users. Such components
allow the user to see, traverse and search the items. Generally, users can do
the "Add", "Edit", and "Delete" operations on the
list too.
- The Master-Detail components that show two list of items. The main list or master
list contain the parent entities and the second list or detail list shows the
childes of the selected item of the master list. Generally, users can do the
"Add", "Edit" and "Delete" operations on both lists.
- The Add-Edit components that show the input controls which allow the user to add
the new entities or edit the current entities.
- The Detail components that show the detail of an item.
- The Main command bar that enable users to select an entity type to see its items.
Although it is not possible to formulate all windows and user controls that exist
in an application, but the above components exist in many applications. Although
such scenarios are very common, however there is not some kind of pattern in
the Composite UI to cover the issue.
In this paper, I create a set of base classes that targets items 1, 3, and 4 in the
above list. In the next paper, I will cover all of the above items and although
a code generator that make it possible to create the presenters easier.
Developers can use the provided library to create the data entry forms in a few seconds.
Composite UI uses the Model-View-Presenter patterns to create different parts
of the user interface. In fact, an application that is based on Composite UI
has some modules, those modules have some Presenters and those Presenters have
one or more views. The modules stick the Views of Presenters to the regions of
the main Shell. Generally, the code of Views is very thin and the communication
among the Presenters will be done by Composite Commands and Composite Events. In this paper, I don't touch the Views, since the code of them is thin, but the
focus is on base classes for Presenters.
A Summary of the Library
The following class diagram illustrates the whole classes in the library.

PresenterBase
It is the base class that both ListBasePresenters and ItemBasePresenters inherit
from. The PresenterBase implements the INotifyPropertyChanged and IDataErrorInfo
interfaces. Putting the implementation of those interfaces in the base class
means there would be less code in concrete classes. The INotifyPropertyChanged
has been used to notify the Views about the changes that have been done on the
Presenter. Implementing the Interface is essential for most of the Presenters
in the WPF platform. The IDataErrorInfo has been used to announce the validation
errors to Views. The interface had existed since .NET 1.1. It was firstly used
in DataSets to propagate the validation errors. Here is the code of the interface.
// Summary:
// Provides the functionality to offer custom error information that a user
// interface can bind to.
public interface IDataErrorInfo
{
// Summary:
// Gets an error message indicating what is wrong with this object.
//
// Returns:
// An error message indicating what is wrong with this object. The default is
// an empty string ("").
string Error { get; }
// Summary:
// Gets the error message for the property with the given name.
//
// Parameters:
// columnName:
// The name of the property whose error message to get.
//
// Returns:
// The error message for the property. The default is an empty string ("").
string this[string columnName] { get; }
}
IDataErrorInfo is my favorite way to propagate the validation errors from Presenters
to Views. It was one the great missing features in the .NET framework 3.0, but
the WPF team in Microsoft realized the issue and supported the interface in the
.NET framework 3.5. Interested reader can find more about the interface and how
to use it here. Here is the code of PresenterBase.
public class PresenterBase : IDataErrorInfo, INotifyPropertyChanged
{
#region IDataErrorInfo
public string Error
{
get
{
return null;
}
}
public string this[string name]
{
get
{
string result = "";
if (errors.ContainsKey(name))
{
result = errors[name];
}
return result;
}
}
private Dictionary<string, string> errors = new Dictionary<string, string>();
protected bool HasError(string key)
{
return errors.ContainsKey(key);
}
protected void AddError(string key, string value)
{
if (errors.ContainsKey(key))
{
errors[key] = value;
}
else
{
errors.Add(key, value);
}
PropertyChanged(this, new PropertyChangedEventArgs(key));
}
protected void ClearError(string key)
{
errors.Remove(key);
PropertyChanged(this, new PropertyChangedEventArgs(key));
}
protected void ClearErrors()
{
for (int i = errors.Count - 1; i > -1; i--)
{
string key = errors.Last().Key;
errors.Remove(key);
PropertyChanged(this, new PropertyChangedEventArgs(key));
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void notifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ListBasePresenter<T, VIEW>
Any presenter that displays a list of items should inherit from this class. This
class provides the basic functionalities that a list-based presenter needs to
have. The first thing about the class is the fact that it is general. In fact,
it should be a generic class so that the concrete classes that inherit from it
could use its functionalities easily. There are two types that should be defined
as generics in this class: 1) the type of the entity that the class shows its
instances 2) the type of the View.
The class has the following properties.
- View: Each Presenter should have at least one View. Putting the code of initializing
the View in the base class would be a good idea, since its code has been repeated
in Presenters
- Items: ListBasePresenter is responsible for showing the items of type T. So, it should
publish a list of type T.
- Title: Hard coding the Title of the Presenter in its View may not be a good idea.
It is better to have a Title property in the Presenter. The View can bind its
title to the Title property of the Presenter.
- IDataService<T>. The Presenter uses an instance of the IDataService<T>
to load/save data. In the next section, I will describe the detail of the IDataService.
- SelectedItem. In order to do the Edit/Delete operations, the Presenter should have
access to the current selected item.
- Add, Edit, and Delete Commands: Views use these commands to make it possible for
the user to do create, edit and delete operations
Here is the code of above properties in the class.
public class ListPresenter<T, VIEW> : PresenterBase
where T : class
where VIEW : ViewBase
{
#region Properties
public virtual string Title
{
get
{
return title;
}
protected set
{
if (title != value)
{
title = value;
notifyPropertyChanged("Title");
}
}
}
private string title;
public ObservableCollection<T> Items
{
get
{
return items;
}
set
{
if (items != value)
{
items = value;
notifyPropertyChanged("Items");
}
}
}
private ObservableCollection<T> items;
public VIEW View { get; set; }
public IUnityContainer container;
public T SelectedItem
{
get
{
return selectedItem;
}
set
{
if (selectedItem != value)
{
selectedItem = value;
notifyPropertyChanged("SelectedItem");
}
}
}
private T selectedItem;
public ICommand EditCommand { get; set; }
public ICommand ViewCommand { get; set; }
public ICommand DeleteCommand { get; set; }
public ICommand AddCommand { get; set; }
public IDataService<T> Service { get; set; }
public IEventAggregator EventAggregator { get; set; }
protected string OnDeleteString;
#endregion
}
As you may notice, Items and SelectedItem property notify their changes using the
notifyPropertyChanged. Notifying the changes is essential in order to announce
the changes of data to Views.
Now, consider the things that the ListBasePresenter has implemented. The first thing
that the presenter should do is initializing. In the initialize process, the
Presenter resolves its members, its views and setting the DataContext of its
views. Here is the initializing code.
public class ListPresenter<T, VIEW> : PresenterBase
where T : class
where VIEW : ViewBase
{
public ListPresenter(IUnityContainer container)
{
this.container = container;
Service = container.Resolve<IDataService<T>>();
View = container.Resolve<VIEW>();
EventAggregator = container.Resolve<IEventAggregator>();
EditCommand = new DelegateCommand<object>(editComandHandler);
AddCommand = new DelegateCommand<object>(addComandHandler);
ViewCommand = new DelegateCommand<object>(viewComandHandler);
DeleteCommand = new DelegateCommand<object>(deleteCommandHandler);
EventAggregator.GetEvent<CompositePresentationEvent<OnItemAddedEventArgs<T>>>().Subscribe(OnNewItemRecevied);
LoadItems();
View.Model = this;
}
}
One of the main members that the Presenter should resolve in the initializing phase
is IDataService<T>. The presenter uses the IDataService<T> to load/save
the data.
The other thing that a ListBasePresenter should do is loading the data. The loading
of data has been done in the LoadItems method. Here is the code of LoadItems.
protected virtual void LoadItems()
{
List<T> list;
Service.Fill(out list
);
Items = new ObservableCollection<T>(list);
}
Whenever user fires the add/edit commands, the presenter should publish an event
so that the ItemBasePresenter<T> can do the appropriate reactions. Here
is the code of publishing the results.
protected virtual void editComandHandler(object parameter)
{
if (SelectedItem == null)
{
return;
}
// Call to edit data
EventAggregator.GetEvent<CompositePresentationEvent<EditItemEventArgs<T>>>().Publish(new EditItemEventArgs<T>(SelectedItem));
}
protected virtual void addComandHandler(object parameter)
{
T newData = (T)Activator.CreateInstance(typeof(T));
EventAggregator.GetEvent<CompositePresentationEvent<AddItemEventArgs<T>>>().Publish(new AddItemEventArgs<T>(newData));
}
protected void OnNewItemRecevied(OnItemAddedEventArgs<T> e)
{
if (!Items.Contains(e.Value))
{
Items.Add(e.Value);
SelectedItem = e.Value;
}
}
protected virtual void viewComandHandler(object parameter)
{
if (SelectedItem == null)
{
return;
}
// Call to edit data
EventAggregator.GetEvent<CompositePresentationEvent<ViewItemEventArgs<T>>>().Publish(new ViewItemEventArgs<T>(SelectedItem));
}
protected virtual void deleteCommandHandler(object parameter)
{
if (MessageBox.Show(OnDeleteString, "Warning", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
Service.Delete(SelectedItem);
int index = Items.IndexOf(SelectedItem);
Items.Remove(SelectedItem);
if (index > 0)
{
SelectedItem = Items[index - 1];
}
else
{
SelectedItem = null;
}
}
}
ItemBasePresenter<T>
Any presenter that is designed to add a new entity, or edit the current entity, or
display the detail of current item should inherit from this base class. The class
provides the following functionalities.
Listening for add, edit or view events
The ListPresenters fires the CompositePresentationEvents to announce their corresponding ItemPresenters about the adding or editing data.
On the other hand, ItemBasePresenters listen to those events to do the corresponding
reaction. The main advantage of using CompositePresentationEvent for communication
among Presenters is the fact that the Presenters do not need to have access to
each other. It is a major advantage that exists throughout the Composite pattern.
Whenever, an ItemPresenter receives such event, it loads the item and displays
its View. Here is the code of registering the methods of the class to listen
to the CompositePresentationEvents.
EventAggregator = container.Resolve<IEventAggregator>();
EventAggregator.GetEvent<CompositePresentationEvent<EditItemEventArgs<T>>>().Subscribe(this.demandToEditItem);
EventAggregator.GetEvent<CompositePresentationEvent<AddItemEventArgs<T>>>().Subscribe(this.demandToAddItem);
EventAggregator.GetEvent<CompositePresentationEvent<ViewItemEventArgs<T>>>().Subscribe(this.demandToViewItem);
Here is the code of the demandToEditItem method.
protected virtual void demandToEditItem(EditItemEventArgs<T> e)
{
this.Mode = ViewMode.Edit;
this.Item = e.Value;
loadData();
PopulateForEdit();
IRegion region = regionManager.Regions[RegionName];
if (!region.Views.Contains(View))
{
region.Add(View);
}
region.Activate(View);
}
In the first line of the method, it sets the Mode to Edit. The Mode enumeration specifies
the operation that the Presenter does. Here are the members of the enumeration.
public enum ViewMode { Add, Edit, View}
In the next lines, the method loads the data and populates the class properties.
Finally it adds its View to the Region of the shell and activates it. For more
information about the Regions and RegionManger, visits here. Here is the code
of demandToAddItem
protected virtual void demandToAddItem(AddItemEventArgs<T> e)
{
this.Item = e.Value;
this.Mode = ViewMode.Add;
this.Item = e.Value;
PopulateForAdd();
IRegion region = regionManager.Regions[RegionName];
if (!region.Views.Contains(View))
{
region.Add(View);
}
region.Activate(View);
}
Saving Data
The saving data is a little complicated. First of all, the validation of the entity
should be checked. Second, the any special process that should be done on the
data before saving should be done. The next step is saving the data and finally
publishing the changes. Here is the whole code of the Presenter.
public class ItemPresenter<T, VIEW> : PresenterBase
where T : class
where VIEW : ViewBase
{
public virtual string Name { get; set; }
public virtual string RegionName { get; set; }
public virtual string Title
{
get
{
return title;
}
protected set
{
if (title != value)
{
title = value;
notifyPropertyChanged("Title");
}
}
}
private string title;
public T Item
{
get
{
return item;
}
set
{
if (item != value)
{
item = value;
notifyPropertyChanged("Item");
}
}
}
private T item;
public IEventAggregator EventAggregator { get; set; }
public IDataService<T> Service { get; set; }
public ViewMode Mode { get; set; }
public VIEW View { get; set; }
protected IRegionManager regionManager;
protected virtual void SetData()
{
}
protected virtual void PopulateForAdd()
{
Title = String.Format("Add {0}", Name);
}
protected virtual void PopulateForEdit()
{
Title = String.Format("Edit {0}", Name);
}
protected virtual void PopulateForView()
{
Title = String.Format("View {0}", Name);
}
protected virtual bool isValid()
{
return true;
}
protected virtual void cancel()
{
}
protected virtual void save()
{
if (Mode == ViewMode.Add)
{
Service.Add(Item);
}
else if (Mode == ViewMode.Edit)
{
Service.Edit(Item);
}
}
protected virtual void close()
{
this.ClearErrors();
regionManager.Regions[RegionName].Deactivate(View);
}
protected virtual void loadData()
{
}
protected virtual void loadRelatedData()
{
}
protected virtual void demandToAddItem(AddItemEventArgs<T> e)
{
this.Item = e.Value;
this.Mode = ViewMode.Add;
this.Item = e.Value;
PopulateForAdd();
IRegion region = regionManager.Regions[RegionName];
if (!region.Views.Contains(View))
{
region.Add(View);
}
region.Activate(View);
}
protected virtual void demandToEditItem(EditItemEventArgs<T> e)
{
this.Mode = ViewMode.Edit;
this.Item = e.Value;
loadData();
PopulateForEdit();
IRegion region = regionManager.Regions[RegionName];
if (!region.Views.Contains(View))
{
region.Add(View);
}
region.Activate(View);
}
protected virtual void demandToViewItem(ViewItemEventArgs<T> e)
{
this.Mode = ViewMode.View;
this.Item = e.Value;
loadData();
PopulateForView();
}
public ItemPresenter(IUnityContainer container, IRegionManager regionManager)
{
this.regionManager = regionManager;
SaveCommand = new DelegateCommand<object>(saveCommandHandler);
CancelCommand = new DelegateCommand<object>(cancelCommandHandler);
Service = container.Resolve<IDataService<T>>();
EventAggregator = container.Resolve<IEventAggregator>();
EventAggregator.GetEvent<CompositePresentationEvent<EditItemEventArgs<T>>>().Subscribe(this.demandToEditItem);
EventAggregator.GetEvent<CompositePresentationEvent<AddItemEventArgs<T>>>().Subscribe(this.demandToAddItem);
EventAggregator.GetEvent<CompositePresentationEvent<ViewItemEventArgs<T>>>().Subscribe(this.demandToViewItem);
loadRelatedData();
View = container.Resolve<VIEW>();
View.Model = this;
}
public ICommand SaveCommand { get; set; }
public ICommand CancelCommand { get; set; }
protected void saveCommandHandler(object parameter)
{
if (isValid())
{
SetData();
save();
close();
if (Mode == ViewMode.Add)
{
EventAggregator.GetEvent<CompositePresentationEvent<OnItemAddedEventArgs<T>>>().Publish(new OnItemAddedEventArgs<T>(Item));
}
else if (Mode == ViewMode.Edit)
{
EventAggregator.GetEvent<CompositePresentationEvent<OnItemEditedEventArgs<T>>>().Publish(new OnItemEditedEventArgs<T>(Item));
}
}
}
protected void cancelCommandHandler(object parameter)
{
close();
}
}
IDataService<T>
Presenters use the IDataService<T> to load/save data. The IDataService<T>
provides the basic CRUD operations on the entities of type T. Here is the code
of IDataService<T> interface.
public interface IDataService<T> where T:class
{
void Add(T t);
void Edit(T t);
void Delete(T t);
void Fill(out List<T> list);
}
Using the Code
The above base classes make the code of ListPresenters very clean and simple. Here
is the code of the AddressesPresenter that is responsible for displaying the
list of addresses. The View associated with the Presenter is of type IAddressView.
public class AddressesPresenter : ListPresenter<Address, IAddressesView>, IAddressesPresenter
{
public AddressesPresenter(IUnityContainer container)
: base(container)
{
Title = "Addresses";
OnDeleteString = "Are you sure you want to delete the selected Address";
}
}
As can be seen, there is just one constructor with two lines of code in the Presenter.
In the first line of the constructor the Title of the Presenter has been set.
This title will be displayed as the title of the View. In the second line, the
OnDeleteString has been set. OnDeleteString is a confirm message that will be
displayed to user to confirm the delete action whenever user selects the delete
operation.
Here is the code of AddressPresenter that is responsible for adding/editing the address
entities.
public class AddressPresenter : ItemPresenter<Address, IAddressView>, IAddressPresenter
{
public AddressPresenter(IUnityContainer container, IRegionManager regionManager)
: base(container, regionManager)
{
Name = "Address";
RegionName = RegionNames.PopupRegion;
}
protected override void PopulateForAdd()
{
base.PopulateForAdd();
Item.rowguid = Guid.NewGuid();
}
protected override void SetData()
{
base.SetData();
Item.ModifiedDate = DateTime.Now;
}
protected override bool isValid()
{
if (Item.City == null)
{
Item.AddError("City", "Error");
return false;
}
if (Item.City != null && Item.City.Length > 20 || Item.City.Trim().Length == 0)
{
Item.AddError("City", "Error");
return false;
}
if (Item.PostalCode== null)
{
Item.AddError("PostalCode", "Error");
return false;
}
if (Item.PostalCode != null && Item.PostalCode.Length > 50 || Item.PostalCode.Trim().Length == 0)
{
Item.AddError("PostalCode", "Error");
return false;
}
if (Item.AddressLine1 == null)
{
Item.AddError("AddressLine1", "Error");
return false;
}
if (Item.AddressLine1 != null && Item.AddressLine1.Length > 50 || Item.AddressLine1.Trim().Length ==
0)
{
Item.AddError("AddressLine1", "Error");
return false;
}
if (Item.CountryRegion == null)
{
Item.AddError("CountryRegion", "Error");
return false;
}
if (Item.CountryRegion != null && Item.CountryRegion.Length > 50 || Item.CountryRegion.Trim().Length
== 0)
{
Item.AddError("CountryRegion", "Error");
return false;
}
if (Item.StateProvince == null)
{
Item.AddError("StateProvince", "Error");
return false;
}
if (Item.StateProvince != null && Item.StateProvince.Length > 50 || Item.StateProvince.Trim().Length
== 0)
{
Item.AddError("StateProvince", "Error");
return false;
}
return true;
}
Actually, classes that inherit from the ItemPresenters should override the PopulateForAdd,
IsValid, and SetData methods. A complete demo can be downloaded here You need VS 2008 and SQL EXPRESS to run the demo.
As you may notice, using the library makes the code of Presenters clean and simple.
In the next paper, I will extend the library to cover more scenarios.