WPF MVVM in Depth

15 Sep 2022

← Home
6 min. read

Hooking up View and ViewModels in MVVM

There are a couple ways to connect Views and ViewModels together in WPF:

View-First Construction

Method 1 - Statically Setting the DataContext

When the code-behind of a xaml view get constructed, it will call the InitializeComponent() method, which will go ahead and scan the xaml constructing the view. If you have a DataContext set in the xaml, then a static instance of the view model will be created during this initialization step.

<UserControl>
    <UserControl.DataContext>
        <local:CustomerListViewModel /> <!-- Notice Here -->
    </UserControl.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding Customers}" />
    </Grid>
</UserControl>

Method 2 - Imperatively Setting the DataContext

You can also set the DataContext from the code behind, like so:

public partial class CustomerListView : UserControl
{
    public CustomerListView()
    {
        this.DataContext = new CustomerListViewModel();
        InitializeComponent();
    }
}

You might want to do this if your view model has any constructor parameters, as xaml cannot handle the passing of arguments to the DataContext.

Using the ViewModelLocator Pattern

ViewModelLocator is a meta-pattern for automatically locating and hooking up the right ViewModel.

Here’s an example of how you could implement a ViewModelLocator:

public static class ViewModelLocator
{
    public static bool GetAutoWireViewModel(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoWireViewModelProperty);)
    }

    public static void SetAutoWireViewModel(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoWireViewModelProperty, value);
    }

    public static readonly DependencyProperty AutoWireViewModelProperty =
        DependencyProperty.RegisterAttached("AutoWireViewModel", typeof(bool),
            typeof(ViewModelLocator), new PropertyMetadata(false, AutoWireViewModelChanged));

    private static void AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) return;
        var viewTypeName = d.GetType().FullName; // get the View's name
        var viewModelTypeName = viewTypeName + "Model";
        var viewModelType = Type.GetType(viewModelTypeName);
        var viewModel = Activator.CreateInstance(viewModelType);
        ((FrameworkElement)d).DataContext = viewModel;
    }
}
ViewModelLocator Process
  1. Determine which View type is being constructed
  2. Determine ViewModel type to create based on convention
  3. Construct ViewModel
  4. Set ViewModel as the View’s DataContext

Then for your Views, you will need to add the following:

<UserControl ...
    xmlns:root="clr-namespace:YourNamespace"
    root:ViewModelLocator.AutoWireViewModel="True">

Advantages include:

  1. Standard declaration in every view
  2. The View is no longer strongly coupled to a particular ViewModel type

ViewModel-First Construction

Before you start with ViewModel-First construction, it’s helpful to have some understanding of how data templates work.

Using Explicit and Implicit DataTemplates

Explicit DataTemplate Example

Below defines a DataTemplate for displaying a Customer object. This way, the ListBox knows how to properly display a Customer object.

<UserControl.Resources>
    <DataTemplate x:Key="CustomerTemplate">
        <StackPanel>
            <TextBlock Text="{Binding FirstName}" />
            <TextBlock Text="{Binding LastName}" />
            <TextBlock Text="{Binding Phone}" />
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

<!-- ... -->

<ListBox ItemsSource="{Binding Customers}" ItemTemplate="{StaticResource CustomerTemplate}" />
Implicit DataTemplate Example
<UserControl.Resources>
    <DataTemplate DataType="{x:Type data:Customer}">
        <StackPanel>
            <TextBlock Text="{Binding FirstName}" />
            <TextBlock Text="{Binding LastName}" />
            <TextBlock Text="{Binding Phone}" />
        </StackPanel>
    </DataTemplate>
</UserControl.Resources>

<!-- ... -->

<ListBox ItemsSource="{Binding Customers}" />

Using DataTemplates for ViewModel-First Construction

<Window.Resources>
    <DataTemplate DataType="{x:Type Customers:CustomerListViewModel}">
        <Customers:CustomerListView />
    </DataTemplate>
</Window.Resources>
<Window.DataContext>
    <local:MainWindowViewModel />
</Window.DataContext>
<Grid>
    <ContentControl Content="{Binding CustomerViewModelFromMainWindowViewModel}" />
</Grid>

Communicating between Views and ViewModels in WPF

Using Commands for View to ViewModel Communication

The most common way to communicate between the View and ViewModel is by using an ICommand in the ViewModel and binding to it in the View.

Below are some examples on how to bind for invocation of the command. See Relay Command for code-behind implementation.

<Button Content="Delete"
    Command="{Binding DeleteCommand}"/>
<UserControl.InputBindings>
    <KeyBinding Key="D" Modifiers="Control"
        Command="{Binding DeleteCommand}" />
</UserControl.InputBindings>

Attached Properties/Behaviors - WIP

Intro to Attached Properties

Intro to XAML Behaviors for WPF

Property Changed Notifications

Hierarchies and Navigation

Sometimes you will want a View to be composed of other Views (and their associated ViewModels).

Encapsulating INotifyPropertyChanged

You can have your ViewModels inherit from ViewModelBase to encapsulate INPC logic.

public class ViewModelBase : INotifyPropertyChanged
{
    protected virtual void SetProperty<T>(ref T member, T val,
        [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(member, val)) return;

        member = val;
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    })

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

You can use a Command with CommandParameter to switch between views:

In the ViewModel:


public RelayCommand<string> NavCommand { get; private set; }

public ViewModelCtor()
{
    NavCommand = new RelayCommand<string>(OnNav);
}

private void OnNav(string destination)
{
    switch(destination)
    {
        case "orderPrep":
            ChildViewModel = _orderPrepViewModel;
            break;
        // case ...:
    }
}

In the XAML:

<Button Command="{Binding NavCommand}"
    CommandParameter="orderPrep"/>

Validation in MVVM

Adding Validation to an Input View

public class ValidatableViewModel : ViewModelBase, INotifyDataErrorInfo
{
    private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        if (_errors.ContainsKey(propertyName))
            return _errors[propertyName];
        else
            return null;
    }

    public bool HasErrors
    {
        get { return _errors.Count > 0; }
    }

    protected override void SetProperty<T>(ref T member, T val, [CallerMemberName] string propertyName = null)
    {
        base.SetProperty<T>(ref member, val, propertyName);
        ValidateProperty(propertyName, val);
    }

    private void ValidateProperty<T>(string propertyName, T value)
    {
        var results = new List<ValidationResult>();
        ValidationContext context = new ValidationContext(this);
        context.MemberName = propertyName;
        Validator.TryValidateProperty(value, context, results);

        if (results.Any())
        {
            _errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
        }
        else
        {
            _errors.Remove(propertyName);
        }
        ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }
}

In XAML, this will check to see if the ViewModel implements INotifyDataErrorInfo:

<TextBox Text="{Binding FirstName, ValidatesOnNotifyDataErrors=True}" />

MVVM Toolkits / Frameworks