In this chapter, we will cover how to hook up ViewModel. It is a continuation of the last chapter in which we discussed the View first construction. Now, the next form of the first construction is a meta-pattern which is known as ViewModelLocator. It is a pseudo pattern and is layered on top of the MVVM pattern.
In MVVM each View needs to be hooked up to its ViewModel.
The ViewModelLocator is a simple approach to centralize the code and decoupling the view more.
It means that it does not have to explicitly know about ViewModel type and how to construct it.
There are a number of different approaches to use ViewModelLocator, but here we use the most similar to the one that's part of the PRISM framework.
ViewModelLocator provides a standard, consistent, declarative and loosely coupled way to do view first construction which automates the process of getting ViewModel hooked up to the View. The following figure represents the high level process of ViewModelLocator.
Step 1 − Figure out which View type is being constructed.
Step 2 − Identify the ViewModel for that particular View type.
Step 3 − Construct that ViewModel.
Step 4 − Set the Views DataContext to the ViewModel.
To understand the basic concept, let's have a look at the simple example of ViewModelLocator by continuing the same example from the last chapter. If you look at the StudentView.xaml file, you will see that we have statically hooked up the ViewModel.
Now as shown in the following program, comment these XAML code also remove the code from Code-behind.
<UserControl x:Class = "MVVMDemo.Views.StudentView" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:local = "clr-namespace:MVVMDemo.Views" xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300"> <!--<UserControl.DataContext> <viewModel:StudentViewModel/> </UserControl.DataContext>--> <Grid> <StackPanel HorizontalAlignment = "Left"> <ItemsControl ItemsSource = "{Binding Path = Students}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation = "Horizontal"> <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" Width = "100" Margin = "3 5 3 5"/> <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" Width = "100" Margin = "0 5 3 5"/> <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" Margin = "0 5 3 5"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Grid> </UserControl>
Now let’s create a new folder VML and add a new public class ViewModelLocator which will contain a single attached property (dependency property) AutoHookedUpViewModel as shown in the following code.
public static bool GetAutoHookedUpViewModel(DependencyObject obj) { return (bool)obj.GetValue(AutoHookedUpViewModelProperty); } public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { obj.SetValue(AutoHookedUpViewModelProperty, value); } // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. //This enables animation, styling, binding, etc... public static readonly DependencyProperty AutoHookedUpViewModelProperty = DependencyProperty.RegisterAttached("AutoHookedUpViewModel", typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false, AutoHookedUpViewModelChanged));
And now you can see a basic attach property definition. To add behavior to the property, we need to add a changed event handler for this property which contains the automatic process of hooking up the ViewModel for View. The code to do this is as follows −
private static void AutoHookedUpViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (DesignerProperties.GetIsInDesignMode(d)) return; var viewType = d.GetType(); string str = viewType.FullName; str = str.Replace(".Views.", ".ViewModel."); var viewTypeName = str; var viewModelTypeName = viewTypeName + "Model"; var viewModelType = Type.GetType(viewModelTypeName); var viewModel = Activator.CreateInstance(viewModelType); ((FrameworkElement)d).DataContext = viewModel; }
Following is the complete implementation of ViewModelLocator class.
using System; using System.ComponentModel; using System.Windows; namespace MVVMDemo.VML { public static class ViewModelLocator { public static bool GetAutoHookedUpViewModel(DependencyObject obj) { return (bool)obj.GetValue(AutoHookedUpViewModelProperty); } public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) { obj.SetValue(AutoHookedUpViewModelProperty, value); } // Using a DependencyProperty as the backing store for AutoHookedUpViewModel. //This enables animation, styling, binding, etc... public static readonly DependencyProperty AutoHookedUpViewModelProperty = DependencyProperty.RegisterAttached("AutoHookedUpViewModel", typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false, AutoHookedUpViewModelChanged)); private static void AutoHookedUpViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (DesignerProperties.GetIsInDesignMode(d)) return; var viewType = d.GetType(); string str = viewType.FullName; str = str.Replace(".Views.", ".ViewModel."); var viewTypeName = str; var viewModelTypeName = viewTypeName + "Model"; var viewModelType = Type.GetType(viewModelTypeName); var viewModel = Activator.CreateInstance(viewModelType); ((FrameworkElement)d).DataContext = viewModel; } } }
First thing to do is to add a namespace so that we can get to that ViewModelLocator type in the root of our project. Then on route element which is a view type, add AutoHookedUpViewModel property and set it to true.
xmlns:vml = "clr-namespace:MVVMDemo.VML" vml:ViewModelLocator.AutoHookedUpViewModel = "True"
Here is the complete implementation of StudentView.xaml file.
<UserControl x:Class = "MVVMDemo.Views.StudentView" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" xmlns:local = "clr-namespace:MVVMDemo.Views" xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel" xmlns:vml = "clr-namespace:MVVMDemo.VML" vml:ViewModelLocator.AutoHookedUpViewModel = "True" mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300"> <!--<UserControl.DataContext> <viewModel:StudentViewModel/> </UserControl.DataContext>--> <Grid> <StackPanel HorizontalAlignment = "Left"> <ItemsControl ItemsSource = "{Binding Path = Students}"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation = "Horizontal"> <TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}" Width = "100" Margin = "3 5 3 5"/> <TextBox Text = "{Binding Path = LastName, Mode = TwoWay}" Width = "100" Margin = "0 5 3 5"/> <TextBlock Text = "{Binding Path = FullName, Mode = OneWay}" Margin = "0 5 3 5"/> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Grid> </UserControl>
When the above code is compiled and executed, you will see that ViewModelLocator is hooking up the ViewModel for that particular View.
A key thing to notice about this is the view is no longer coupled in a way to what the type of its ViewModel is or how it gets constructed. That’s all been moved out to the central location inside the ViewModelLocator.