After the posts (table of contents) in which I covered why we need an agile UI design pattern, it’s big picture and the needed tools, I start digging into sample code. I’ll show in each post a small part of the whole picture. If you want to get all at once then you find the source of all samples at http://sourceforge.net/projects/procollee. ProCollEE is my playground to experiment with WPF and UI design.
Lets start
Yes, let’s start. But where?
There is one UI design pattern – presenter first (link) – that explicitly states where to start in its name. But unfortunately, the presenter first pattern packs too many responsibilities into the presenter. Therefore, not the best source to get an answer from.
But we have to start somewhere, don’t we?
In my experience, I normally know what has to be visualized in a dialog or window, but I don’t know yet how exactly the data is visualized or where to get it from.
View-Model
Therefore, let’s start with what we know.
ProCollEE – the application I use as a sample – is an application for team collaboration. Its very basic feature is to send a message on one or more channels to all people that registered one of these channels. If you receive a message then it is shown in a dashboard containing all messages of the channels you registered. (note that ProCollEE is in a very very early stage of development).
Okay, then let’s start with the entry of a message. The UI should allow me to enter a message text, select the channels on which I want to send the message and sending the message.
As I explained in the post about the big picture, it is the responsibility of the view-model to bridge the likely complex domain model to a simplified model used for the current screen. Therefore, the view-model should provide me with a message text, the selected channels and an action to send the message.
These requirements led me to this:
/// <summary> /// The view model for the text entry view. /// </summary> public class MessageEntryViewModel : ViewModelBase, IMessageEntryViewModel { /// <summary> /// <see cref="PropertyChangedEventArgs"/> for <see cref="Text"/> /// </summary> private static readonly PropertyChangedEventArgs TextChangedEventArgs = CreatePropertyChangedEventArgs<MessageEntryViewModel>(x => x.Text); /// <summary> /// <see cref="PropertyChangedEventArgs"/> for <see cref="Text"/> /// </summary> private static readonly PropertyChangedEventArgs SendMessageCommandChangedEventArgs = CreatePropertyChangedEventArgs<MessageEntryViewModel>(x => x.SendMessageCommand); /// <summary> /// The view model of the channel selection. /// </summary> private readonly IChannelSelectionViewModel channelSelectionViewModel; /// <summary> /// backing field for <see cref="Text"/> /// </summary> private string text; /// <summary> /// backing field for <see cref="SendMessageCommand"/> /// </summary> private ICommand sendMessageCommand; /// <summary> /// Initializes a new instance of the <see cref="MessageEntryViewModel"/> class. /// Used for design time support. /// </summary> public MessageEntryViewModel() { } /// <summary> /// Initializes a new instance of the <see cref="MessageEntryViewModel"/> class. /// </summary> /// <param name="channelSelectionViewModel">The channel selection view model.</param> public MessageEntryViewModel(IChannelSelectionViewModel channelSelectionViewModel) { this.channelSelectionViewModel = channelSelectionViewModel; } /// <summary> /// Gets or sets the text. /// </summary> /// <value>The text of the message.</value> public string Text { get { return this.text; } set { this.text = value; this.OnPropertyChanged(TextChangedEventArgs); } } /// <summary> /// Gets the channels. /// </summary> /// <value>The channels.</value> public IEnumerable<string> Channels { get { return this.channelSelectionViewModel.SelectedChannels; } } /// <summary> /// Gets or sets the command used to send messages. /// </summary> /// <value>The send text command.</value> [Inject, Named("SendMessageCommand")] public ICommand SendMessageCommand { get { return this.sendMessageCommand; } set { this.sendMessageCommand = value; this.OnPropertyChanged(SendMessageCommandChangedEventArgs); } } }
Yes, of course, this wasn’t the first shot, but it was what I ended up with.
Let’s take a closer look
Properties for the View
The main part of the ViewModel class is the three properties Text, Channels and SendMessageCommand. These properties are provided to the view that will visualize them (but that’s part of another post). This is the part I start with because it’s the easiest. These are the things that I know have to be on the view. As I said earlier in this post, the message entry dialog should provide me with the possibility to enter a message, choose the channels to send it on and to actually send it.
The ViewModel provides the property Text that holds the message.
The property Channels holds the chosen channels. As you can see, this is delegated to an embedded view-model. We’ll see later in this series, how I handle hierarchical views/view-models.
Finally, the SendMessageCommand property provides the action to take to send the message. The attributes defined on the property are used to get Ninject inject the correct instance into the property when the instance of the view-model is created.
PropertyChanged
I want that changes to the property values can be tracked by other interested objects. Therefore, I implemented the property changed pattern – each property setter fires a PropertyChanged event when its value is set. This will allow the view to update when data is changed.
The static fields and the base class (ViewModelBase) are used to have type-safe property changed event invokers.
Conclusions
That’s it for today. I introduced the view-model that takes the responsibility to act as a bridge between the domain model and the data to be visualized in a dialog or window. This is accomplished by providing the data the view will visualize to the user. The data representation is decoupled from the domain model (you didn’t even see a bit of it yet, did you?) to allow a simple view implementation.
By the way, the view is the next thing we’ll take a look at. See you next time…