DataContext And Autowire In WPF

First of all, Happy New Year to my fellow community. I am wishing that the new year will bring  joy, love, peace, and happiness to you and your family. It's been a while since I wrote an article. (Not to mention the pandemic.) But let's keep learning.
 
Today we are going to cover one of the basics of WPF. You can even say, everything else is useless if you don't get this step right. With that being said, let's jump into today's topic.
 
DataContext is the head of everything. It makes sure that your View is hooked up with ViewModel.
 
There are 3 ways to hook-up View with ViewModel. 
  1. Within XAML
  2. Code-Behind
  3. ViewModelLocator 
Our focus is how to bind DataContext so we are not going to focus on styling or data in this article.
 
We need 2 folders, one each for View & ViewModel.Then I have created two UserControls,  LoginView and RegisterView with their respective ViewModels  1. LoginViewModel & 2. RegisterViewModel. Refer to figure 1 to understand the structure.
 
Note
We have to follow the standard here. Every view ends up with a term "View" such as LoginView &  every viewmodel ends up with a term "ViewModel" such as LoginViewModel. 
 
DataContext And Autowire In WPF
Figure 1
 

Binding DataContext within XAML

 
We are going to use LoginView for this experiment,
 
Step 1
 
Add namespace of LoginViewModel to LoginVIew.xaml.
  1. xmlns:local="clr-namespace:WPF_DataContext.VIewModel" 
Step 2
 
Use UserControl's DataContext property to assign ViewModel
  1. <UserControl.DataContext>  
  2.      <local:LoginViewModel/>  
  3. </UserControl.DataContext>                
That's all. Now to confirm if View is hooked-up with ViewModel or not we can add TextBlock in LoginView.xaml. Once you've done that your final LoginView.xaml will look like this. 
  1. <UserControl x:Class="WPF_DataContext.View.LoginView"    
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
  6.              xmlns:local="clr-namespace:WPF_DataContext.VIewModel"    
  7.              xmlns:ViewModelWire ="clr-namespace:WPF_DataContext"    
  8.              mc:Ignorable="d"     
  9.              d:DesignHeight="450" d:DesignWidth="800"    
  10.              Height="40" Width="200">    
  11.     <UserControl.DataContext>    
  12.         <local:LoginViewModel/>    
  13.     </UserControl.DataContext>    
  14.     <Grid>    
  15.         <TextBlock Text="{Binding Message}" HorizontalAlignment="Center"/>    
  16.     </Grid>    
  17. </UserControl>     
Note that TextBlock is bound with property name Message, and for that we need to create this string type property in LoginVIewModel. Let's assign some value to Message in a constructor.
 
Tip
 
It is a bad practice to assign values or call APIs in constuctor, you should always use methods with multi-threading for calling or updating data. But since this is a small example we are going to assign values directly in the constructor.
  1. namespace WPF_DataContext.VIewModel  
  2. {  
  3.     public class LoginViewModel  
  4.     {  
  5.         private string _message;  
  6.   
  7.         public string Message  
  8.         {  
  9.             get { return _message; }  
  10.             set { _message = value; }  
  11.         }  
  12.   
  13.         public LoginViewModel()  
  14.         {  
  15.             _message = "Login View Model is Connected..";  
  16.         }  
  17.     }  

Finally, just call LoginView UserControl in MainWindow.xaml, as follows.
  1. <Window x:Class="WPF_DataContext.MainWindow"    
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
  6.         xmlns:local="clr-namespace:WPF_DataContext"     
  7.         xmlns:localView="clr-namespace:WPF_DataContext.View"    
  8.         mc:Ignorable="d"    
  9.         Title="MainWindow" Height="450" Width="800">    
  10.     <Grid>    
  11.         <localView:LoginView/>    
  12.     </Grid>    
  13. </Window>    
Now, run your project. You will be able to see output as figure 2,
 
DataContext And Autowire In WPF
Figure 2
 

Assigning DataContext with Code-Behind

 
In this approach, we are going to use RegisterView. Open code-behind class of RegisterView, which is RegisterView.xaml.cs and set this.DataContext value.
  1. this.DataContext = new RegisterViewModel(); 
Your final RegiserView.xaml.cs will look like the following code snippet,
  1. using System.Windows.Controls;  
  2. using WPF_DataContext.VIewModel;  
  3.   
  4. namespace WPF_DataContext.View  
  5. {  
  6.     /// <summary>  
  7.     /// Interaction logic for RegisterView.xaml  
  8.     /// </summary>  
  9.     public partial class RegisterView : UserControl  
  10.     {  
  11.         public RegisterView()  
  12.         {  
  13.             InitializeComponent();  
  14.             this.DataContext = new RegisterViewModel();  
  15.         }  
  16.     }  

That's all. Pretty simple, huh?
 
Now we have TextBlock on View, we have set up DataContext in code-behind and we need to assign values to that TextBlock in ViewModel.
 
The following RegisterView.xaml shows how TextBlock looks and follwoing code snippet shows what RegisterViewModel looks like.
 
RegisterView.xaml
  1. <UserControl x:Class="WPF_DataContext.View.RegisterView"    
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
  6.              xmlns:local="clr-namespace:WPF_DataContext.View"    
  7.              xmlns:ViewModelWire ="clr-namespace:WPF_DataContext"    
  8.              mc:Ignorable="d"     
  9.              d:DesignHeight="450" d:DesignWidth="800">    
  10.     <Grid>    
  11.         <TextBlock Text="{Binding Message}" HorizontalAlignment="Center"/>    
  12.     </Grid>    
  13. </UserControl>     
RegisterViewModel.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace WPF_DataContext.VIewModel  
  8. {  
  9.     public class RegisterViewModel  
  10.     {  
  11.         private string _message;  
  12.   
  13.         public string Message  
  14.         {  
  15.             get { return _message; }  
  16.             set { _message = value; }  
  17.         }  
  18.   
  19.         public RegisterViewModel()  
  20.         {  
  21.             _message = "Register View Model is Connected..";  
  22.         }  
  23.     }  

Finally, replace LoginView with RegisterView in Mainwindow.xaml 
  1. <Window x:Class="WPF_DataContext.MainWindow"      
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"      
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"      
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"      
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"      
  6.         xmlns:local="clr-namespace:WPF_DataContext"       
  7.         xmlns:View="clr-namespace:WPF_DataContext.View"      
  8.         mc:Ignorable="d"      
  9.         Title="MainWindow" Height="450" Width="800">      
  10.     <Grid>      
  11.         <View:RegisterView/>      
  12.     </Grid>      
  13. </Window>     
This output of this example will look like figure 3
 
DataContext And Autowire In WPF
 Figure 3
 

Auto-wire with ViewModelLocator

 
ViewModelLocator centralizes the code to hookup View & ViewModel. Which means it provides us loosely coupled way to bind View with ViewModel, with this approach, View does not need to hardcode the ViewModel it is getting hooked up with. So basically we automate this entire process in a five step approach,
  • Step 1: Determine View, For e.g. LoginView.
  • Step 2: Determine ViewModel, If we follow naming conventions correctly, then it is always ViewModel at the end View's name. For e.g.   LoginViewModel.
  • Step 3: Once you have a type of a ViewModel from step 2, you need to create an instance of that ViewModel type.
  • Step 4: Crate an instance of ViewModel
  • Step 5: Set a DataContext of a View.
Note
All these steps take place at runtime, that's why View doesn't have to worry about it's ViewModel binding at compile time.
 
Now that we got this logic setteled, we can simply wrap it inside a method to then reuse the same method. Next, we need to figure out how to call this method from View, and there's a simple way to do that, this can be achieved with the help of attached property.
 
Let's begin this process by adding a class named ViewModelLocator,
  • Class ViewModelLocator will encapsulate
  1. One attached propery named : AutoWireViewModel
  2. and one event handler named : AutoWireViewModelChanged (This method will consist of 4 steps mentioed above)
 Your final ViewModelLocator will look like this: Note: Check summary & comments in code to undetstand the meaning.
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Linq;  
  5. using System.Text;  
  6. using System.Threading.Tasks;  
  7. using System.Windows;  
  8.   
  9. namespace WPF_DataContext  
  10. {  
  11.     public static class ViewModelLocator  
  12.     {  
  13.         /// <summary>  
  14.         /// Gets AutoWireViewModel attached property  
  15.         /// </summary>  
  16.         /// <param name="obj"></param>  
  17.         /// <returns></returns>  
  18.         public static bool GetAutoWireViewModel(DependencyObject obj)  
  19.         {  
  20.             return (bool)obj.GetValue(AutoWireViewModelProperty);  
  21.         }  
  22.   
  23.         /// <summary>  
  24.         /// Sets AutoWireViewModel attached property  
  25.         /// </summary>  
  26.         /// <param name="obj"></param>  
  27.         /// <param name="value"></param>  
  28.         public static void SetAutoWireViewModel(DependencyObject obj, bool value)  
  29.         {  
  30.             obj.SetValue(AutoWireViewModelProperty, value);  
  31.         }  
  32.   
  33.         /// <summary>  
  34.         /// AutoWireViewModel attached property  
  35.         /// </summary>  
  36.         public static readonly DependencyProperty AutoWireViewModelProperty =  
  37.             DependencyProperty.RegisterAttached("AutoWireViewModel",  
  38.             typeof(bool), typeof(ViewModelLocator),  
  39.             new PropertyMetadata(false, AutoWireViewModelChanged));  
  40.   
  41.         /// <summary>  
  42.         /// Step 5 approach to hookup View with ViewModel  
  43.         /// </summary>  
  44.         /// <param name="d"></param>  
  45.         /// <param name="e"></param>  
  46.         private static void AutoWireViewModelChanged(DependencyObject d,  
  47.             DependencyPropertyChangedEventArgs e)  
  48.         {  
  49.             if (DesignerProperties.GetIsInDesignMode(d)) return;  
  50.             var viewType = d.GetType(); //Step 1: Ex- LoginView  
  51.             var viewModelTypeName = (viewType).ToString().Replace("View""ViewModel"); //Step 2: Ex- LoginViewModelName  
  52.             var viewModelType = Type.GetType(viewModelTypeName); // step 3: Ex- get the type of LoginViewModel  
  53.             var viewModel = Activator.CreateInstance(viewModelType); // step 4: Ex- create an instance of LoginViewModel  
  54.             ((FrameworkElement)d).DataContext = viewModel; // step 5: Ex- LoginView's DataContext is set to LoginViewModel              
  55.         }  
  56.     }  

So far we have crated an attached property which calls our AutoWire logic with its event. This is a boolean type of attached property which will trigger the event when it is set to true. Let's see how we can do that in a View.
 
First, add a namespace of a LoginViewModel to your View
  1. xmlns:ViewModelWire ="clr-namespace:WPF_DataContext" 
 Second, set AutoWireViewModel to true 
  1. ViewModelWire:ViewModelLocator.AutoWireViewModel="True" 
 Now comment the context that we added earlier in this view. at last, you have a LoginView.xaml which would look like this,
  1. <UserControl x:Class="WPF_DataContext.View.LoginView"    
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
  6.              xmlns:local="clr-namespace:WPF_DataContext.VIewModel"    
  7.              xmlns:ViewModelWire ="clr-namespace:WPF_DataContext"    
  8.              mc:Ignorable="d"     
  9.              d:DesignHeight="450" d:DesignWidth="800"    
  10.              Height="40" Width="200"    
  11.              ViewModelWire:ViewModelLocator.AutoWireViewModel="True">    
  12.     <!--<UserControl.DataContext>    
  13.         <local:LoginViewModel/>    
  14.     </UserControl.DataContext>-->    
  15.     <Grid>    
  16.         <TextBlock Text="{Binding Message}" HorizontalAlignment="Center"/>    
  17.     </Grid>    
  18. </UserControl>     
To cofirm that it can work with multiple view at a time, let's do few changes in RegisterView as well. And don't forget to comment DataContext which we set in a code-behind of RegisterView.xaml.cs.
 
Final RegisterView.xaml
  1. <UserControl x:Class="WPF_DataContext.View.RegisterView"    
  2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     
  5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     
  6.              xmlns:local="clr-namespace:WPF_DataContext.View"    
  7.              xmlns:ViewModelWire ="clr-namespace:WPF_DataContext"    
  8.              mc:Ignorable="d"     
  9.              d:DesignHeight="450" d:DesignWidth="800"    
  10.              ViewModelWire:ViewModelLocator.AutoWireViewModel="True">    
  11.     <Grid>    
  12.         <TextBlock Text="{Binding Message}" HorizontalAlignment="Center"/>    
  13.     </Grid>    
  14. </UserControl>     
Finally, to see this in action, add both of the Views to MainWindow.xaml
 
MainWindow.xaml
  1. <Window x:Class="WPF_DataContext.MainWindow"    
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
  4.         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"    
  5.         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
  6.         xmlns:local="clr-namespace:WPF_DataContext"     
  7.         xmlns:localView="clr-namespace:WPF_DataContext.View"    
  8.         mc:Ignorable="d"    
  9.         Title="MainWindow" Height="450" Width="800">    
  10.     <Grid>    
  11.         <Grid.RowDefinitions>    
  12.             <RowDefinition/>    
  13.             <RowDefinition/>    
  14.         </Grid.RowDefinitions>    
  15.         <localView:LoginView/>    
  16.         <localView:RegisterView Grid.Row="1"/>    
  17.     </Grid>    
  18. </Window>     
Now, run the project & enjoy the work of automation.
 
Figure 4
Perfect..
 

Summary

 
Today we learned what are different ways to bind View with ViewModel, how to use it & how to automate it with AutoWire. We learned that it is always a best practice to use AutoWireLocator. I hope that this article was helpful enough for you & I hope you gained some knowledge out of this.
 
Keep coding, and stay healthy.
 
There is one big piece of news coming soon. Tune in and I  will update you about it in upcoming articles.