Dajbych.net


MVVM: Model-View-ViewModel

, 9 minutes to read

net2002 logo

Model-View-ViewModel is a design pattern for WPF applications. It provides a solution to separate the application logic from the user interface. There is less code, everything is clearer and any changes are not an implementation nightmare. MVVM separates data, application state, and user interface. WPF itself was built to make MVVM comfortable to use. Therefore, it uses binding and command – a replacement for the event-driven user interface.

In most desktop applications, there is a process going on in the background, and windows are used to consume information from that process. It is desirable that the process is window-independent. This allows the window to be closed without affecting it and to easily share information between multiple windows. One solution is Model-View-ViewModel, which is a tried and tested solution. For example, Expression Blend is programmed according to this pattern.

Principle

The main idea of MVVM is simple – to create a class that keeps the state of the application. It is called a ViewModel. This is queried by the user interface, which renders the controls according to it. Conversely, if the user enters any data into the user interface, it is automatically propagated to the ViewModel. WPF is well suited for this use, because the binding allows you to declaratively link the user interface to the ViewModel.

ViewModel is the most important class. It provides all the data for the user interface, which is called the View. Importantly, it provides its data in data structures that trigger events when they change. This allows the UI to automatically display new data as it changes in the ViewModel. The ViewModel has two cornerstones. The first is the ObservableCollection<T> collection, which reports when an element is added or removed. The second is the INotifyPropertyChanged interface. Describes an event that occurs when any of the properties of a ViewModel are changed.

Layers

The model describes the data that the application works with. If you're using Code-First Entity Framework, the classes that map to database tables are Models. If you're referencing a web service, the classes that Visual Studio generates for you are also Models. The model must not know anything about the state of the controls.

View represents the user interface in XAML. This can be an application window, a page, or a control. It is also referred to as UI, form, or presentation layer, but it is still the same.

The ViewModel associates the Model and the View and holds the state of the application. Controls are bound to the ViewModel and draw their content from it. It filters data depending on the state of the application. To propagate its properties to a View, it must implement the INotifyPropertyChanged interface. Similarly, its collection-type properties must implement the INotifyCollectionChanged interface.

Sample

Using a primitive sample application number 4, which uses SQL Server Express and Entity Framework 4.2, we will show how MVVM is implemented. The application is designed to display photos of civil airliners. Each aircraft is stored with its photos in the database. A model is a class that is used by Code-First Entity Framework as a database model. The ViewModel contains a list of all aircraft. The user interface displays a list of aircraft and photos of the aircraft that is currently selected. The basic decomposition looks like this:

The models are data classes Aircraft and Image, to which Entity Framework maps a database represented by class Database. View is XAML markup that describes how the user interface looks. The ViewModel is the most important part, which groups data classes, takes care of business logic, and projects data into the user interface. The ViewModel binds to the UI by assigning an instance of the ViewModel in the View to the DataContext property.

The sample ViewModel offers a list of aircraft in the Aircrafts property of type ObservableCollection<Aircraft>. It does not implement the INotifyPropertyChanged interface because it has no features to take advantage of this interface.

public class WorkspaceViewModel {

    public WorkspaceViewModel() {
        using (var db = DemoDb.Connection) {
            Aircrafts = new ObservableCollection<Aircraft>(db.Aircrafts.Include(a => a.Images));
        }
    }
    
    public ObservableCollection<Aircraft> Aircrafts { get; private set; }
    
}

ViewModel must have a public constructor if we want to create an instance of it in XAML. It should be right, but if the ViewModel is to be a singleton, there is no choice but to set DataContext from the code behind. In order to create an instance in XAML, you need to map the ViewModel namespace to the XML namespace:

xmlns:wm="clr-namespace:Dajbych.Demo4.ViewModels"

This is how the DataContext is set in XAML:

<Window.DataContext>
    <wm:WorkspaceViewModel/>
</Window.DataContext>

Binding

When a View has its data context, it can connect to its data using a binding:

<Label Content="{Binding Name}" />
<ListBox ItemsSource="{Binding Aircrafts}" Name="aircraftList" SelectedValuePath="Images" />
<ListBox ItemsSource="{Binding Path=SelectedValue, ElementName=aircraftList}" />

The first label asks the ViewModel for a property named Name, which logically returns string. However, type quality is not checked at this level. Any error will only become apparent when the application is running.

The first listbox asks ViewModel for a property called Aircrafts and expects a collection. If a collection implements the INotifyCollectionChanged interface, which ObservableCollection<T> implements, the changes to that collection will be reflected in the listbox. And since ObservableCollection<T> implements the Collection<T> interface, changes in the listbox will be reflected in the ViewModel as well.

The second ListBox also has a binding, but this time it is not connected to the ViewModel but to the View. The ElementName parameter specifies the name of the control that is used as the data source. The Path parameter specifies that the data source will not be a listbox, but its property SelectedValue. The SelectedItem property is held by the currently selected element of type Aircraft from the collection of type ObservableCollection<Aircraft>. The property SelectedValue holds some property of his, which is determined by the property of SelectedValuePath. Because it is set to Images, the property SelectedValue is of type ICollection<Image>.

Data Template

A DataTemplate is a template for a single element of a collection. It can use binding, where the template data context is just the element of the collection that has been assigned by parameter ItemsSource.

<ResourceDictionary>
    <DataTemplate x:Key="image">
        <Image Source="{Binding Uri}" Width="655" />
    </DataTemplate>
</ResourceDictionary>

<ListBox ItemsSource="{Binding Path=SelectedValue, ElementName=aircraftList}" ItemTemplate="{StaticResource image}" />

Listbox has its data context of the ICollection<Image>* type. This means that the template has its data context of type Image. Type Image has a property Uri of type Uri, which is referenced by the control Image of template image. We do not have to define the template separately, but we can insert it directly into the element:

<ListBox ItemsSource="{Binding Path=SelectedValue, ElementName=aircraftList}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding Uri}" Width="655" />
        </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

Data Template Selector

In the next sample application number 5, we will demonstrate DataTemplateSelector. We will use it when we have objects in the collection inherited from the same abstract class, but they are instances of different classes. In our example, the abstract class is AircraftViewModel, from which classes AirbusViewModel and BoeingViewModel inherit. Listbox has set ItemTemplateSelector:

<ListBox ItemsSource="{Binding Aircrafts}" ItemTemplateSelector="{StaticResource templateSelector}" />

ItemTemplateSelector determines the specific DataTemplate based on the type of class instance:

public class PropertyDataTemplateSelector : DataTemplateSelector {

    public DataTemplate Boeing { get; set; }
    public DataTemplate Airbus { get; set; }
    
    public override DataTemplate SelectTemplate(object item, DependencyObject container) {
    
        if (item is AirbusViewModel) return Airbus;
        
        if (item is BoeingViewModel) return Boeing;
        
        throw new Exception();
    }
}

DataTemplateSelector is then set in ResourceDictionary:

xmlns:s="clr-namespace:Dajbych.Demo5.Selectors"

<ResourceDictionary>
    <s:PropertyDataTemplateSelector x:Key="templateSelector" Airbus="{StaticResource airbus}" Boeing="{StaticResource boeing}" />
</ResourceDictionary>

Command

In the last example number 6, we will introduce Command. It is a ViewModel method that is called by View when the user presses a button (or any other control implementing the ICommandSource interface). The ViewModel exposes this method as ICommand:

public class WorkspaceViewModel {
    public WorkspaceViewModel() {
        Switch = new Command<Machine>(ChangeMachine);
    }

    public ICommand Switch { get; private set; }
    
    private void ChangeMachine(Machine machine) { }
}

View can invoke the method by setting the Command property on the button. At the same time, it can also pass a method parameter, typically a selected element:

<Button Command="{Binding Switch}" CommandParameter="{Binding ElementName=listbox, Path=SelectedItem}" />

Class Command<T> is a particular implementation of the ICommand interface. It can contain logic to enable or disable command invocation in method CanExecute. This is manifested by the possible disabling of the button.

public class Command<T> : ICommand where T : class {
    private Action<T> execute;
    private Func<T, bool> enabled;

    public Command(Action<T> execute, Func<T, bool> enabled = null) {
        this.execute = execute;
        this.enabled = enabled;
    }

    public event EventHandler CanExecuteChanged {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter) {
        if (enabled == null) {
            return true;
        } else {
            return enabled.Invoke(parameter as T);
        }
    }

    public void Execute(object parameter) {
        execute.Invoke(parameter as T);
    }
}

Converter

Casting in a binding often finds its application. If we want to show or hide a control, we can represent that property with the bool type and convert it to the enumeration type Visbility in the converter. This way, the ViewModel does not have to expose properties whose type is too tied to the presentation layer. However, in Example 6, the conversion from Machine to Uri is used:

[ValueConversion(typeof(Machine), typeof(Uri))]
public class MachineToUriConverter : IValueConverter {

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        if (value != null) {
            var machine = value as Machine;
            if (machine != null) {
                return new Uri(machine.Url);
            } else {
                throw new ArgumentException("Type Machine expected.");
            }
        } else {
            return null;
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
        throw new Exception("One way only.");
    }
    
}

The conversion class passes values both ways, because binding works bidirectionally. In this particular case, however, we will use only one direction. The conversion class must be registered in ResourceDictionary:

<Application.Resources>
    <ResourceDictionary>
        <c:MachineToUriConverter x:Key="MachineToUriConverter" />
    </ResourceDictionary>
</Application.Resources>

Then we can use the conversion in the binding:

<Image Source="{Binding Selected, Converter={StaticResource MachineToUriConverter}}" />

Finally

The Model-View-ViewModel design pattern provides a concrete solution to approach the architecture of the application. Its benefits lie in easier maintenance, expandability and testability.