Archive

Posts Tagged ‘.NET’

MVVM pattern in Silverlight and WPF

March 5, 2011 5 comments

First this is not some newbie tutorial on what is MVVM and how to use it in .NET. This article mostly about problem’s that you will face in real life applications using MVVM pattern with WPF and Silverlight vanilla controls library and how to solve them.

A lot has been written on how WPF and Silverlight  are great with MVVM pattern. And probably you all read books about XAML, WPF, Silverlight where author’s on super simplified examples show how it is easy to bind this to that – 2 minutes and the job is done. But in real life applications situation is not so nice like it is shown in pretty useless (for real application developers) technology overview books. Before I start showing what’s wrong with MVVM in Silverlight and WPF and how to overcome it, I would like to define my position on subject: MVVM is a great pattern, WPF and Silverlight control’s library which is shipped with NET 4.0 is far from perfect for data binding development (especially Silverlight’s).

Let’s start, so what’s wrong? To demonstrate this let’s try to develop some real life application using MVVM. After 2 minutes of thinking I decided to develop a plant classification application. I’m not biologist and I know nothing about plant classification, but it doesn’t matter in our context. So how the application will look like:

User Interface

Now let’s describe our application UI:

We will have plant’s category tree to the left, and to the right we will have a table showing plant’s belonging to selected category. In the toolbar at the top we will have CRUD operations buttons. Table columns could very depending on selected plant category.

So we have a pretty standard catalog application, let’s try to code it using MVVM pattern in Silverlight (because WPF supports almost everything that Silverlight can do we will take the minimal profile – Silverlight (suppose we are targeting both WPF and Silverlight)).

First let’s start by writing our ViewModel class properties:

public IList Categories {  get; set; } // Category TreeView
public Category ActiveCategory { get; set;  } // Active Category (selected node of TreeView)
public IList  Plants { get; set; } // Plant list for active category (DataGrid's rows)
public IList SelectedPlants { get; set; } // Plants selected  in table (Datagrid's selected rows)
public Plant ActivePlant { get; set; }  // Active plant (active DataGrid row)
public IList Columns {  get; set; } // Columns of plant's table (Datagrid's columns)

Look’s like this model fully represent our View state information, now let’s add some logic and behavior we need. We need to update our table (rows, and columns headers) when selected Category changes, also we want to make the first category selected when application start’s, and finely we need create, delete, edit operation’s to work with plants. So our MainViewModel with logic and behavior will look like this:

public class MainViewModel :  INotifyPropertyChanged
{
public IPlantService Service;
public  IViewManager ViewManager;
private Category _activeCategory;

public MainViewModel()
{
Plants = new List();
Categories =  Service.GetCategories();
ActiveCategory = Categories[0]; //Let's make the  first category selected and active by default.
}

#region Implementation of INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

public void InvokePropertyChanged(string property)
{
if  (PropertyChanged != null)
{
PropertyChanged(this, new  PropertyChangedEventArgs(property));
}
}

#endregion

public IList Categories { get; set; }

public Category ActiveCategory
{
get { return _activeCategory; }
set
{
_activeCategory = value;
InvokePropertyChanged(“ActiveCategory”);
OnCategoryChanged();
}
}

public IList Plants { get; set; }
public IList  SelectedPlants { get; set; }
public Plant ActivePlant { get; set; }
public IList Columns { get; set; }

private void OnCategoryChanged()
{
//when new ctegory is selected we  need to update our table,
//show plant list, and update table columns.
Plants.Clear(); //We should clear the plant list before changing table  columns.
Columns = Service.GetColumnsForCategory(ActiveCategory);
Plants  = Service.GetPlantsForCategory(ActiveCategory);
}

public void DeletePlants()
{
foreach (Plant plant in SelectedPlants)
{
Plants.Remove(plant);
Service.DeletePlant(plant);
}
}
public void EditPlant()
{
Plant editPlant = ActivePlant;
ViewModel view = ViewManager.CreateView(editPlant);
//I'm using async  event here in case the view is created as not modal window
view.OnResult +=  (bool resultSuccess) =>
{
if (!resultSuccess) Service.ReloadPlant(ref  editPlant);//in case user canceled operation we need to rollback plan't to it's  original state,
//we do this simply by reloading it from data store.
else Service.SavePlant(editPlant);
};
}
public void NewPlant()
{
Plant newPlant = new Plant(); //We do not want to add it to  Model.Plants collection yet because this will show empty plant in table,
//we want' to show new plant only after user presses OK (commit's the input)
ViewModel view = ViewManager.CreateView(newPlant);
view.OnResult +=  (bool resultSuccess) =>
{
if (resultSuccess)
{
Plants.Add(newPlant);
Service.SavePlant(newPlant);
}
};
}
}

So far all look’s pretty nice and clean. We have created working ViewModel which represents our abstract UI with UI logic. Now it’s time to create our View and wire it to ViewModel. Let’s start by designing our View in XAML, we will not bother about ToolBar, MainMenu, StatusBar for now, first we should create our skeleton – TreeView and DataGrid.
Our MainView code-behind will look like this:

public partial class MainPage :  UserControl
{
public MainViewModel Model { get; set; }
public  MainPage()
{
InitializeComponent();
Model = new MainViewModel();
DataContext = Model;
}
}

And our XAML:


 xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation%22">http://schemas.microsoft.com/winfx/2006/xaml/presentation"</a>
 xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml%22">http://schemas.microsoft.com/winfx/2006/xaml"</a>
 xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008%22">http://schemas.microsoft.com/expression/blend/2008"</a>
 xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006%22">http://schemas.openxmlformats.org/markup-compatibility/2006"</a>
 mc:Ignorable="d"
 d:DesignHeight="500" d:DesignWidth="800"  xmlns:sdk="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk%22">http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"</a>>

All right now the interesting part, let’s wire MainViewModel with this View. Let’s start with TreeView, we need to wire Items and SelectedItem.

Binding TreeView.Items to  ViewModel.Categories:


Hmm strange we can see tree item’s but instead of Category.Name we see Type information, look’s like DisplayMemberPath=”Name” is ignored, not a problem we can add Category.ToString() method returning Name, also this can be fixed by specifying Binding.Path in DataTemplate as will shown latter. All right this look’s ok.

Binding TreeView.SelectedItem to ViewModel.ActiveCategory:
Ummm there is no intellisense when we type SelectedItem in XAML, that’s because it is a ReadOnly property, and there is no simple and clean way to wire it, all right let’s leave it for now and move on.

DataGrid: we need items, selected items, current item (the one that has focus when multiple item’s selected), and columns. To be able to see data(rows) in DataGrid we first need to add column’s, so let’s start from columns.

Binding DataGrid.Columns to  ViewModel.Columns:

Let’s see Columns definition in DataGrid – it’s public ObservableCollection<DataGridColumn> Columns { get; }, ummmm it’s ReadOnly property and it’s even not DependencyProperty which means we can’t data bind to it, hmm this getting annoying… all right let’s leave it for now and switch on AutoGenerateColumns="True" which will add Column for each property.

Binding DataGrid.Items to ViewModel.Plants:
This is very easy:


that’s it, executing it to make sure it works.. all works fine.

Binding DataGrid.SelectedItem to ViewModel.ActivePlant:

Our DataGrid support’s multi selection, so the user can select many rows and delete them for example. What about when the user decides to do edit operation, well for such case there is a current row or focused row in DataGrid, there can be only one current row at a time. But there is no such property in DataGrid, but there are 2 candidates for such functionality it is CurrentCellChanged event (but no CurrentCell property so this is unbindable), and there is SelectedItem property which is easy bindable because it’s read|write DependencyProperty. And according to MSDN it will contain the current row if multiple rows selected (or last index row if current row is not among the selected rows) so it will do the job.


Binding DataGrid.SelectedItems to ViewModel.SelectedPlants:

Let’s look at DataGrid.SelectedItems property public IList SelectedItems { get; } it’s not DependencyProperty and it’s ReadOnly. Arghhh this is really getting annoying, all right let’s leave it for now.

Some conclusion and thought’s for part 1:

So from your 6 properties we were able to bind only 3 using vanilla Silverlgiht 4 control library with provided data binding capabilities , and that’s for such a simple project.

Some of reader’s may be wondering why I’m creating DataGrid’s columns from ViewModel instead of creating separate XAML Views for each Plant Type, anyway I will have to define Column list somewhere be it XAML, ViewModel.Columns collection (which can be loaded from data store). Well first I want to reuse as much code as possible, second I think that dynamic UI programing is what we all need to do. Dynamic User Interface is interface that is constructed at runtime and not defined at design time. As an example of good dynamic UI application take a look at implementation of Visual Studio’s MainMenu, ToolBar, Commands, you can customize them anyway you like. Same every good application with table allows the user to select which columns to display, this is all good examples of using dynamic UI. And as we can clearly see from our efforts Silverlight’s (and WPF’s) controls are not very good in dynamic programming with MVVM pattern. When it comes to binding some interface part’s (like columns, menus) things get pretty ugly.

Source code for this part of article.

Part II.

In this part I’m going to discuss what approaches and solutions we can use to overcome data binding limitations we have faced in Part I. We will start from the most obvious solution and then refactor it to a more advanced and reusable.

So, how we can resolve our situation? Control’s properties we need to bind to either read only or are not DependencyProperty which  means that they do not signal when their value changes, but there are plain old .NET events like SelectedIndexChanged which we can use to get notification from Control, and from the ViewModel side we have implement INotifyPropertyChanged so no problem there, all we need to do to maintain View with ViewModel wired, is sync them when property value on any side (view or VIewModel) changes. Let’s try this on TreeView SelectedItem which we were unable to bind before.First updating ViewModel.ActiveCategory from the TreeView.SelectedItem:

public partial class MainPage :  UserControl
{
public MainViewModel Model { get; set; }
public  MainPage()
{
InitializeComponent();
Model = new MainViewModel();
DataContext = Model;
uiCategory.SelectedItemChanged += delegate {  Model.ActiveCategory = (Category) uiCategory.SelectedItem; };
}
}

That was pretty easy now the update operation from ViewModel to View, that’s not so easy because TreeView.SelectedItem is read only and we can’t use it to select tree node, googling for Silverlight treeview set selecteditem and we find out that we can use TreeViewItem.IsSelected = true for this purpose,. As you remember we are settings ActiveCategory to a first item in Categories collection in our MainViewModel, so the first item in our TreeView should be selected during application start, but ActiveCategory is initialized in ViewModel constructor before we subscribe to INotifyPropertyChanged so we need to get this value in View constructor and set TreeView.SelectedItem, as you can see things get complicated and ugly, all right let’s try it: ((TreeViewItem) uiCategory.Items[uiCategory.Items.IndexOf(Model.ActiveCategory)]).IsSelected = true; That doesn’t work because our uiCategory.Items[] are of Type Category and not TreeVewItem, we need to get TreeViewItem from our bound object. This can be done using TreeView.ItemContainerGenerator.ContainerFromItem


public MainPage()
{
 InitializeComponent();
 Model = new  MainViewModel();
 DataContext = Model;
 uiCategory.SelectedItemChanged += delegate { Model.ActiveCategory = (Category)  uiCategory.SelectedItem; };
 ((TreeViewItem)uiCategory.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected  = true;  //Will not work
}

And guess what this will not work either, will return NULL object!  After some thinking on subject I came to conclusion that it returns null because the TreeView at this moment is not yet bound to Category collection, I tried moving this line inside MainPage.Loaded event, TreeView.Loaded event but without any success, the last event that get fired when the control is initializing is LayoutUpdate, and theoretically it should fire after binding is complete and the control render it self, let’s try it:


public partial class MainPage : UserControl
{
 public  MainViewModel Model { get; set; }
 public MainPage()
 {
 InitializeComponent();
 Model = new MainViewModel();
 DataContext = Model;
 uiCategory.SelectedItemChanged +=  delegate { Model.ActiveCategory = (Category) uiCategory.SelectedItem; };
 uiCategory.LayoutUpdated += delegate {  ((TreeViewItem)uiCategory.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected  = true; };
 }
}

And indeed it does work. All right now we need to update TreeView.SelectedItem when MainViewModel.ActiveCategoryChanges, se do this be subscribing to MainViewModel.PropertyChanged event, and select TreeViewItem using same method.


public MainPage()
{
 InitializeComponent();
 Model = new  MainViewModel();
 Model.PropertyChanged += ModelPropertyChanged;
 DataContext = Model;
 uiCategory.SelectedItemChanged += delegate {  Model.ActiveCategory = (Category) uiCategory.SelectedItem; };
 uiCategory.LayoutUpdated += delegate {  ((TreeViewItem)uiCategory.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected  = true; };
}

void ModelPropertyChanged(object sender,  System.ComponentModel.PropertyChangedEventArgs e)
{
 switch  (e.PropertyName)
 {
 case "ActiveCategory":
 ((TreeViewItem)uiCategory.ItemContainerGenerator.ContainerFromItem(Model.ActiveCategory)).IsSelected  = true;
 break;
 }
}

But that’s not all bugs of TreeView, TreeView.ItemContainerGenerator know only about top level items to get child items you need to traverse the tree inside, I will not post code here because it’s big and ugly, some example and explanation can be read in this MSDN page (it’s showing WPF example): http://msdn.microsoft.com/en-us/library/ff407130.aspx

As you can see the TreeView control is far from perfect, it is not suitable for MVVM development, and moreover it is hard to use in any development.

But let’s get back to our application, we have finished with TreeView, but the code we have written is not reusable, we don’t want to write this ugly code in every ViewModel in which we going to use TreeView or other control which doesn’t support binding we need out-of-the-box.

How to make code reusable.

What options we have to make the code we wrote above reusable?

  • We can create a derived control with DependencyProperties we need. (either hiding base properties(because for some reason almost all control’s properties are not virtual) or declaring it with a different name).
  • We can create custom attached property where we will put our update logic.

The choice depends on situation,for our example application let’s use attached properties and try to make DataGrid.Columns binding work.

Making DataGrid.Columns bindable using attached properties.

The first thing you need to do is determine how we going to notify View from ViewModel about changes. There are 2 Notifications we need to handle: PropertyChanged when we set Columns property and CollectionChanged when we add or remove items from Columns. First let’s look at updating DataGrid.Columns on PropertyChanged event using attached property.


public class DataGridExtender
{
 public static DependencyProperty  BindingColumnsProperty = DependencyProperty.RegisterAttached("BindingColumns",
 typeof(ObservableCollection),
 typeof(DataGridExtender),
 new PropertyMetadata(
 new PropertyChangedCallback
 (OnPropertyChange)));
 public static void  SetBindingColumns(DependencyObject element,  ObservableCollection value)
 {
 element.SetValue(BindingColumnsProperty, value);
 }
 public static  ObservableCollection GetBindingColumns(DependencyObject  element)
 {
 return  (ObservableCollection)element.GetValue(BindingColumnsProperty);
 }

private static void OnProperyChange(DependencyObject d,  DependencyPropertyChangedEventArgs e)
{
 DataGrid dataGrid =  (DataGrid)d;
 ObservableCollection value =  (ObservableCollection)e.NewValue;
 value.CollectionChanged += delegate { UpdateColumns(dataGrid,value); };
 UpdateColumns(dataGrid, value);
}
private static void  UpdateColumns(DataGrid dataGrid, IEnumerable columns)
{
 dataGrid.Columns.Clear();
 foreach (DataGridColumn  dataGridColumn in columns)
 {
 dataGrid.Columns.Add(dataGridColumn);
 }
}

}

And XAML code:


As you see we have attached BindingColumns property and set up binding it’s value to MainViewModel.Columns, also we declared a Converter which should convert our TableColumn to DataGridColumn object, here it is:


public class TableColumnConverter : IValueConverter
{
 #region  Implementation of IValueConverter

public object Convert(object value, Type targetType, object parameter,  CultureInfo culture)
 {
 ObservableCollection ourColumns =  (ObservableCollection) value;
 ObservableCollection resultColumns = new  ObservableCollection();
 foreach (TableColumn  tableColumn in ourColumns)
 {
 resultColumns.Add(new  DataGridTextColumn
 {
 Header = tableColumn.Name,
 Binding = new Binding {Path = new  PropertyPath(tableColumn.BindingProperty)}
 });
 }
 return resultColumns;
 }

public object ConvertBack(object value, Type targetType, object  parameter, CultureInfo culture)
 {
 throw new  NotImplementedException();
 }

#endregion
}

How this all works? When the View is initialized the binding engine gets ViewModel.Columns and try to set it value to DataGrid.BindingColumns attached property, to do so it first need to convert ObservableCollection<TableColumn> which we have in ViewModel, to type declared by BindingColumns attached property which is ObservableCollection<DataGridColumn>, for this task it will use TableColumnConverter  we provided, after conversion is complete BindingColumns property is set to result of conversion (ObservableCollection<DataGridColumn> resultColumns) and DataGridExtender.OnPropertyChange get executed where we actually add DataGrid columns. But this code is not complete, it covers only the situation when ViewModel.Columns is set, what will happen if we do ViewModel.Columns.Add(…) ? – nothing, DataGrid will not display added column because of the way IValueConverter works, let’s examine this situation.

Making IValueConverter work with ObservableCollection CollectionChanged event.

What’s wrong with the code above? Why adding items to MainViewModel.Columns collection doesn’t change anything in UI. Let’s look at our TableColumnConverter : IValueConverter Covert method:


public class TableColumnConverter : IValueConverter
{

public object Convert(object value, Type targetType, object parameter,  CultureInfo culture)
 {
 ObservableCollection ourColumns =  (ObservableCollection) value;
 ObservableCollection resultColumns = new  ObservableCollection();
 foreach (TableColumn  tableColumn in ourColumns)
 {
 resultColumns.Add(new  DataGridTextColumn
 {
 Header = tableColumn.Name,
 Binding = new Binding {Path = new  PropertyPath(tableColumn.BindingProperty)}
 });
 }
 return resultColumns;
 }

}

The problem lies in incorrect work of binding engine and Value Converts, binding engine uses CollectionChanged event of collection which is returned from Converter as a trigger to update UI, so instead of subscribing to our MainViewModel.Columns.CollectionChanged event’s it subscribes to ObservableCollection<DataGridColumn> resultColumns = new ObservableCollection<DataGridColumn>(); which we create during conversion, so it have no idea that we have changed ViewModel.Columns collection. To demonstrate this issue you can add following code in Converter method just before the return ResultColumns:


DispatcherTimer dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = new TimeSpan(0, 0, 1);
dispatcherTimer.Tick  += delegate { resultColumns.Add(new DataGridTextColumn(){Header = "Timer",  Binding = new Binding("")}); };
dispatcherTimer.Start();

So instead of updating our UI on Columns CollectionChange event, binding engine does this when a temp collection is changed we use during conversion. One of the solutions to this problem can be synchronizing  converted collection with our original ViewModel.Columns collection, so when we add items to ViewModel.Columns they are added to converted collection as well, which triggers the UI update.


public class TableColumnConverter : IValueConverter
{
 #region  Implementation of IValueConverter

public object Convert(object value, Type targetType, object parameter,  CultureInfo culture)
 {
 ObservableCollection ourColumns =  (ObservableCollection) value;
 ObservableCollection resultColumns = new  ObservableCollection();
 foreach (TableColumn  tableColumn in ourColumns)
 {
 resultColumns.Add(new  DataGridTextColumn
 {
 Header = tableColumn.Name,
 Binding = new Binding {Path = new  PropertyPath(tableColumn.BindingProperty)}
 });
 }
 new CollectionSynchronizer(ourColumns,  resultColumns, new TableColumn2DataGridColumn());
 return  resultColumns;
 }

public object ConvertBack(object value, Type targetType, object  parameter, CultureInfo culture)
 {
 throw new  NotImplementedException();
 }

#endregion
}

public class CollectionSynchronizer
{
 private readonly  ObservableCollection _source;
 private readonly  ObservableCollection _target;
 private readonly  IValueConverter _valueConverter;

public CollectionSynchronizer(ObservableCollection  source,
 ObservableCollection target, IValueConverter  valueConverter)
 {
 _source = source;
 _target =  target;
 _valueConverter = valueConverter;
 //
 _source.CollectionChanged += SourceCollectionChanged;
 }

private void SourceCollectionChanged(object sender,
 System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
 {
 if (e.NewItems != null)
 {
 foreach  (TableColumn tableColumn in _source)
 {
 DataGridColumn column =
 (DataGridColumn)
 _valueConverter.Convert(tableColumn, typeof  (DataGridColumn), null, CultureInfo.CurrentCulture);
 _target.Add(column);
 }
 }
 else if  (e.OldItems != null)
 {
 throw new  NotImplementedException();
 }
 }
}

public class TableColumn2DataGridColumn : IValueConverter
{
 #region Implementation of IValueConverter

public object Convert(object value, Type targetType, object parameter,  CultureInfo culture)
 {
 TableColumn tableColumn =  (TableColumn) value;
 return new DataGridTextColumn() {Header =  tableColumn.Name};
 }

public object ConvertBack(object value, Type targetType, object  parameter, CultureInfo culture)
 {
 throw new  NotImplementedException();
 }

#endregion
}

Final Source code for this article.

Final thoughts.

We have examined the abilities of Silverlight SDK (Controls library) to be used with MVVM pattern. During the development of small and simple Silverlight application we have faced lot’s of problems that are not easy to solve, though we were able to resolve them using different workarounds and hacks, the amount of code we were forced to write is much larger then the code for this application using others UI design patterns or Frameworks.

My personal experience and thoughts on subject: Silverlight and WPF control libraries are written very poorly, there are lot’s of bugs, and problems with them,they are not suitable for real world application development using MVVM pattern for now (.NET 4.0).

Use 3rd party controls libraries, or write your own.