DataModel-View-ViewModel used in a WPF Application
The last week I have been researching patterns & practices! MVP, MVC, DM-V-VM was all to confusing for me...
[EDIT] Josh has e xcellent presentation (with code) about using MVC pattern in WPF (Available here)
I found a excellent series by Dan Carevier about using the DM-V-VM pattern in WPF and decided to try and use it!
Lets start by defining why I wanted to use it. DM-V-VM basically offered 2 advantages that I wanted to achieve:
1) Asynchronously retrieve data from my data source. I have a large data set and wanted my application to be able to retrieve this data in the background.
2) Separating the UI from the data source or in DM-V-VM terms: Separating the DataModel (Data Source) from the View (UI). The benefit of doing this is that the UI is completely skinnable! Different views can be loaded dynamically changing the appearance of the application completely! In MVC/MVP terms, this separation also allows easily changing from technology by just implementing a new View (IE. moving from WinForms to WPF, etc).
To demo the DM-V-VM pattern I needed a large data set. Since many people are Facebook-aholics (with 400+ friends) it seemed like the perfect data set. Lets start with the DataModel.
The DataModel's main purpose is to expose the data in a way that is easily consumable by a WPF application. In WPF terms, this means implementing INotifyCollectionChanged and INotifyPropertyChanged! My DataModel is very similar to the one described by Dan Carevier, The only small change is that I also implemented a debug check to verify that when a property changed notification is raised, that the property actually exists (Taken from Josh Smith's BindableObject). In his article Josh also mention a method of reducing managed heap fragmentation by caching the PropertyChangedArgs. I did not implement this in my example thou!
Here is the code for my base DataModel:
public class DataModelBase : INotifyPropertyChanged
{
protected Dispatcher _dispatcher;
public DataModelBase()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
private ModelState _state;
public ModelState State
{
get
{
VerifyCalledOnUIThread();
return _state;
}
set
{
VerifyCalledOnUIThread();
if (value != _state)
{
_state = value;
SendPropertyChanged("State");
}
}
}
[Conditional("Debug")]
protected void VerifyCalledOnUIThread()
{
Debug.Assert(Dispatcher.CurrentDispatcher == _dispatcher, "Call must be made on UI thread.");
}
private PropertyChangedEventHandler _propertyChangedEvent;
public event PropertyChangedEventHandler PropertyChanged
{
add
{
VerifyCalledOnUIThread();
_propertyChangedEvent += value;
}
remove
{
VerifyCalledOnUIThread();
_propertyChangedEvent -= value;
}
}
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
Type type = this.GetType();
PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
{
Debug.Fail("VerifyProperty");
}
}
protected void SendPropertyChanged(string propertyName)
{
VerifyProperty(propertyName);
VerifyCalledOnUIThread();
if (_propertyChangedEvent != null)
{
_propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
}
|
This is a very basic base class. I define a state for the data (Fetching, Invalid or Active). There is also a check to ensure that the data is only read from the UI thread. The last part of this class handles the notification when properties change.
Next, lets implement a FriendDataModel. The purpose of the FriendDataModel is to retrieve the User class from the Facebook service asynchronously.
class FriendDataModel : DataModelBase
{
private FacebookService _service;
private string _ID;
private User _friend;
public User Friend
{
get
{
VerifyCalledOnUIThread();
return _friend;
}
set
{
VerifyCalledOnUIThread();
_friend = value;
SendPropertyChanged("Friend");
}
}
public FriendDataModel(string ID, FacebookService service)
{
_ID = ID;
_service = service;
State = ModelState.Fetching;
if (!ThreadPool.QueueUserWorkItem(new WaitCallback(FetchFriend)))
{
State = ModelState.Invalid;
}
}
private void FetchFriend(object state)
{
User u = _service.GetUserInfo(_ID)[0];
if ( u != null )
{
this._dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
new ThreadStart(delegate
{
this.Friend = u;
this.State = ModelState.Active;
}));
}
else
{
this._dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle,
new ThreadStart(delegate
{
this.State = ModelState.Invalid;
}));
}
}
}
|
Ok, lets examine this class. The FriendDataModel takes the ID of the friend to be retrieved and the instance of the Facebook service. The constructor sets the state to fetching and the queue a user work item. Now the FetchFriend can asynchronously get the User object for the provided ID. Once the User object has been retrieved, the FetchFriend updates it's status by using the dispatcher.
Now we have a working FriendDataModel, lets test it. I created a very basic WPF application with only a Listbox named FriendsListBox. The magic now happens in the code behind file.
I create a instance of the Facebook service and set my ApplicationKey and Secret (For more detail on how to setup the Facebook service, have a look at this CodeProject article).
Now we can call the lightweight GetFriendsIds that returns a collection of Ids for all your friends (Do not call GetFriends as this will return all the detail for all your friends, this takes forever to load). All that is left now is to iterate through this collection and add a FriendDataModel for each of these ids to a ObservableCollection and bind the ListBox to this! Here is the code:
void Window1_Loaded(object sender, RoutedEventArgs e)
{
s.ApplicationKey = "[YOUR APPLICATION KEY]";
s.Secret = "[YOUR SECRET]";
s.IsDesktopApplication = true;
ObservableCollection<FriendDataModel> friendsDM = new ObservableCollection<FriendDataModel>();
FriendsListBox.ItemsSource = friendsDM;
Collection<string> friends = s.GetFriendIds();
foreach (string id in friends)
{
friendsDM.Add(new FriendDataModel(id, s));
}
} |
This will start to collect friends details once you have logged into Facebook. Add the following DataTemplate to see how the states of all your friends gets updated
<DataTemplate DataType="{x:Type local:FriendDataModel}">
<StackPanel>
<TextBlock Text="{Binding Friend.FirstName}"/>
<TextBlock Text="{Binding Friend.LastName}"/>
<TextBlock Text="{Binding State}" />
</StackPanel>
</DataTemplate>
|
This is all for the first part...
I will go into more detail about the ViewModel and View in the next article.
If you ever tried to write a desktop facebook application you would know that it takes time to load all your friends. This DataModel speed this up by returning immediately to the UI and then doing all the work (Retrieving Friend details) in the background.
For more WPF related resource, check out the WPF Rock Star list