Binding your View to your ViewModel in Wpf
Overview
When you are using Mvvm you need a way to bind your view to the ViewModel.
While this is always done by binding the DataContext Property of a View to an instance of the specific ViewModel class, there are generally two different scenarios:
- The ViewModel can be retrieved using the current DataContext.
- The ViewModel needs to be retrieved from a global source or created on demand.
While the first first scenario is straightforward, the second is a little more a tricky and in this article I'll show a simple pattern that you can use in your applications to simplify the binding process.
Different Scenarios
If the ViewModel can be retrieved using the current DataContext of a View, then I'll call this scenario "Hierarchical ViewModels" and if this is not the case, then I'll call them "Independent ViewModels".
Hierarchical ViewModels
Background
In the first scenario, you are already within a view that is bound to a ViewModel and you want to bind a child view to a ViewModel that is different than the one in the current DataContext, but one that it holds a reference to.
Often the parent is created by a DependencyInjection container that supplies the child ViewModel to the parent inside it's constructor on instantiation.
The parent than exposes the child ViewModel via a public property.
In the parent view, you can just create a binding that sets the DataContext of the child to this property.
Example
In the following section, I've included an example for a hierarchical setup.
Consider this example consisting of two Views:
- MasterView
- DetailView
that are bound to these two ViewModels:
- MasterViewModel
- DetailViewModel
MasterViewModel.cs
public class MasterViewModel
{
public DetailViewModel Detail { get; set; }
...
}
DetailViewModel.cs
public class DetailViewModel
{
...
}
Then the following code can be used to bind the DetailView
inside of the MasterView
to the DetailViewModel
contained inside the MasterViewModel
s Detail property:
MasterView.xaml
<v:DetailView DataContext={Binding Detail} />
Note: As the Source of the Binding defaults to the current DataContext, the Source property of the Binding does not need to be set explicitly and the the Binding needs only to contain the path to the ViewModel.
Independent ViewModels
Background
Then there there are Independent ViewModels, which don't know of each other. This is the case for all base ViewModels, who are the "entry points" for all hierarchical setups.
Some examples:
- The first ViewModel that is bound to a view, eg.
MainViewModel
, when there is no existing DataContext - Independent ViewModels for cross cutting concerns like
- navigation eg. the Menu
- information eg. the Statusbar and Notifications
In those cases, there current DataContext of the View does not contain a property that we can use, so we need to access some kind of central Locator or Factory, which is probably backed by an IoC Container that knows how to retrieve or create the requested ViewModel.
Example
Now setting up independent ViewModels or the first ViewModel when no DataContext is yet available, requires a little more work.
I've come with this simple pattern to make the ViewModels available for binding inside each view.
Create a Locator that exposes the ViewModels that your views require via properties.
- Add an instance of the locator as a static resource to your Application. This should be done on Application Startup, for example by:
- Creating an Instance declaritively in your app.xaml file
- Subscribing to your applications Startup event and setting it using the
Resources
property.
- Bind the View to the ViewModel by using the Locator as a Source using the StaticResource Binding.
Locator
As the first step, we need to create a locator that exposes the ViewModels that will be requested by from within a view.
The Locator exposes the ViewModels as properties, that makes binding against them easy using the normal binding syntax.
You can take the manual approach, where you implement each new ViewModel as a new property of the Locator yourself or you can use a dynamic approach where the ViewModels are lookup by your dependency injection container.
An example for the manual approach would look like something like this:
Locator.cs (manual)
public class Locator
{
public MainViewModel { get { return new MainViewModel(); }}
}
As the manual approach gets tedious really fast, I've opted for the dynamic approach.
My implementation is based on DynamicObject that allows me to forward the property accessor to a dependency resolver for fulfillment.
Locator.cs (dynamic)
/// <summary>Locator that forwards property access to the Dependency Resolver</summary>
public class Locator : DynamicObject
{
/// <summary>Gets or sets the resolver that is used to map a property access to an instance</summary>
public Func<string, object> Resolver { get; private set; }
public Locator(Func<string, object> resolver)
{
this.Resolver = resolver;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
bool successful;
string property = binder.Name;
var resolver = this.Resolver;
if (resolver != null)
{
try
{
result = resolver(property);
successful = true;
}
catch { result = null; successful = false; }
}
else
{
result = null;
successful = false;
}
return successful;
}
}
The locator is supplied with a Func in it's constructor that resolves the request for the ViewModel based on the requested property name.
For example, let's say you are using Autofac as a dependency resolver and you've configured Autofac to resolve your ViewModels by looking them up from your ViewModel
namespace using a simple convention like this:
ContainerBuilder builder = new Autofac.ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.InNamespace("WpfMvvmExample.ViewModel");
var container = builder.Build();
Now you can create an instance of the Locator like this:
var locator = new Locator(property => container.Resolve(Type.GetType($"WpfMvvmExample.ViewModel.{property}")));
Note: As the binding path used inside a view is just a string and evaluated at runtime and not strongly typed, using a dynamic object fits nicely, as we don't lose any type information.
Add the initialized Locator
Now it's time to add the Locator to someplace where your views can access it. The Application_Startup
method inside the App
class is a good place for that.
App.xaml.cs
ContainerBuilder builder = new Autofac.ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.InNamespace("WpfMvvmExample.ViewModel")
.SingleInstance();
var container = builder.Build();
this.Resources["Locator"] = new Locator(property => container.Resolve(Type.GetType($"WpfMvvmExample.ViewModel.{property}")));
Bind the View to the ViewModel
Finally we can use the Locator inside of a view to use it to bind to a ViewModel. We reference the Locator as the bindings Source property and point the path property to the required ViewModel type.
MainWindow.xaml
<v:MainView DataContext="{Binding MainViewModel, Source={StaticResource Locator}}" />
Example Code on github
I've uploaded an small example application to github that contains the Locator and the setup in case it is useful for anybody else.
If you have any comments please feel free to drop me a line in the comments below, thanks!
Take care,
-Martin