|
Quite often what happens to me is that I will be using what I think of as a "trick" or a "really cool way of doing things" only to find out months later that what I was doing actually has a name. For example, I once went through an entire conception-to-production life cycle of an ASP.NET application where I enforced model-view-presenter constraints on code separation and yet I didn't know that people already had coined the MVP and MVC patterns and given them names and even published them in books!
So I've been toying with WPF data binding a lot lately, mostly because many of the samples you see for data binding (with the exception of some awesome blogs like Pavan Podila's or Bea Costa's) are naive and have no real value in a real-world application. Writing a class where the constructor is responsible for pre-loading the items in a collection to which the GUI will bind is neither scalable nor useful. What you need is a way for WPF to create an instance of the model object declaratively via the ObjectDataProvider tag and a way for your code to access that data to manipulate it without violating the boundaries of model/view/controller. In other words, to keep the MVC boundaries clean, your code-behind cannot ask the VIEW for a reference to the MODEL. Many samples cheat by grabbing the object data provider using the Resources dictionary from a code-behind - this is a flat-out violation of MVC and simply not good enough for me.
This is when I stumbled on Monostate. I'd used the pattern before, but I finally found a book that gave it a name - Agile Principles, Patterns, and Practices in C# by Robert C Martin and Micah Martin. Monostate is a really simple concept - you can instantiate an infinite number of objects, but all publicly accessible properties refer to the same set of data. In other words, the private members are static and the public members are not. What this enables you to do is allow WPF to create its own private instance of your model object ( you are using model objects, aren't you?? ) and you can create your own instance of the model object from your controller/presenter. Changes made by the controller/presenter are then automatically reflected in the GUI because your model objects are either classes that implement INotifyPropertyChanged or they are ObservableCollections of classes that implement INotifyPropertyChanged.
Take the following code, which provides for a Customer class with a FirstName and LastName property. There is also an ObservableCollection<Customer> that can be used as a binding source for list-type items in XAML:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace MonostateDemo.Model
{
public class Customer : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
public Customer()
{
}
public Customer(string firstName, string lastName)
{
_firstName = firstName;
_lastName = lastName;
}
public string FirstName
{
get { return _firstName; }
set { _firstName = value; NotifyChanged("FirstName"); }
}
public string LastName
{
get { return _lastName; }
set { _lastName = value; NotifyChanged("LastName"); }
}
private void NotifyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class CustomerList : ObservableCollection<Customer>
{
public CustomerList()
{
}
}
}
With this class in place, you can create a resource dictionary that contains a data template that will bind to a new class I called ModelRoot, which basically just exposed public monostate properties, one of which is called Customers, which is an auto-instantiated object of type CustomerList. The following is the code for the resource dictionary DataTemplates.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:MonostateDemo.Model;assembly=MonostateDemo.Model"
>
<ObjectDataProvider x:Key="DataModel" ObjectType="{x:Type model:ModelRoot}"/>
<DataTemplate x:Key="customerTemplate">
<StackPanel>
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding LastName}"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
You may notice that the ModelRoot class is actually contained in an Assembly outside the main application Assembly. You are separating your model from the application's executable Assembly, aren't you? :) So, with a data template in place that can be used to render individual customers, all we have to do now is create a list box that uses the object provider as the items source and customerTemplate as the items template:
<Window x:Class="MonostateBindingDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MonostateBindingDemo" Height="300" Width="300"
>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="DataTemplates.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel>
<ListBox ItemsSource="{Binding Path=Customers, Source={StaticResource DataModel}}"
ItemTemplate="{StaticResource customerTemplate}"/>
</StackPanel>
</Window>
At this point, you've got a list box that is bound to an instance of ModelRoot, specifically to the Customers property of ModelRoot. You can then use your own code to create your own instance of ModelRoot and manipulate the Customers property - and your changes will be automatically reflected in the GUI. Because Customer implements INotifyPropertyChanged, the WPF GUI will actually recognize when changes take place to individual customers as well as when customers are added to or removed from the collection. Here's the code for my Window1.xaml.cs that sets up a couple fake customers and sets up a timer that changes the first customer's last name to the current time, just so you can watch the change take place in the GUI:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using MonostateDemo.Model;
using System.Timers;
namespace MonostateBindingDemo
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : System.Windows.Window
{
public Window1()
{
InitializeComponent();
ModelRoot root = new ModelRoot();
root.Customers.Add(new Customer("Kevin", "Hoffman"));
root.Customers.Add(new Customer("Bob", "Johnson"));Timer t = new Timer(3000);
t.Elapsed += new ElapsedEventHandler(t_Elapsed);
t.Start();
}
void t_Elapsed(object sender, ElapsedEventArgs e)
{
ModelRoot root = new ModelRoot();
root.Customers[0].LastName = DateTime.Now.ToLongTimeString();
}
}
}
I encourage you to copy this code, create your own application and run it. Then, experiment with adding more and more real-world functionality to it. Make a button that clears the list of customers and re-loads them from a database. This is a fantastic way of binding to data in a WPF application and one that, in my opinion, matches really well with real-world application development needs - but you may need to kick the tires on it to prove it to yourself.
Nice work! Looks like it's time to refactor some of my code :)
I am new to the WPF,can you show the code for the ModelRoot class. I
realize that it is in a different assembly.
Hey sir,
i think your complicating this thing by not explaining in detail what is
ModelRoot class, i dont think we the beginners can get into it without the
code or a thorough explanation, plzz
ModelRoot is a class that I created that contains properties that point to
the rest of my model. They are _instance_ properties that point to _static_
member variables, which is the essential core of the Monostate pattern.
This was the ModelRoot class that I came up with. Worked fine with Kevin's
code.
Nice job Kevin, it's working out really well for us. A few issues
with the View and referencing of external assemblies. I have seen them
documented as future fixes for Orca.
public class ModelRoot
{
public ModelRoot()
{
}
private static CustomerList _customerList = new CustomerList();
public CustomerList Customers
{
get
{
return _customerList;
}
}
}
//Some classes to show the difference between Monostate and Singleton
//Simple Monostate class
public class Monostate
{
private static int num_;
public int Num_
{
get
{
return num_;
}
set
{
num_ = value;
}
}
}
//Simple Singleton class
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton Instance
{
get
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}