Using a Design-Time ViewModelLocator With Caliburn.Micro


Unfortunately, design-timesupport out of the box is limited to ViewModels with a public parameterless constructor 1 . This works for a while, but it becomes tedious to duplicate and maintain code that only exists for design-time support. I now use a DesignTimeViewModelLocatorclass for binding sample data in the designer.

Here’s how it works:

The XAML Side d:DataContext="{Binding Source={d:DesignInstance GlobalFiltersViewModel}, Converter={x:Static Global:DesignTimeViewModelLocator.Instance}}" d:DataContextindicates that we’re setting the design-time DataContext for the view. Binding Source={d:DesignInstance GlobalFiltersViewModel}instructs the designer in this case to use a GlobalFiltersViewModelinstance for this view. Converter={x:Static Global:DesignTimeViewModelLocator.Instance}means that we won’t directly use the GlobalFiltersViewModelas the DataContext, but will instead pass it to the DesignTimeViewModelLocatorto be converted to the actual value we want to use as the DataContext. Note that I’m using a static property here since the project is a class library and doesn’t have a global app.xaml resource dictionary that I can use to create the DesignTimeViewModelLocator. The C# implementation using Caliburn.Micro;using System;using System.Linq;using System.Windows.Data;using System.Globalization;using Global.Models;namespace Global{ public class DesignTimeViewModelLocator : IValueConverter { public static DesignTimeViewModelLocator Instance = new DesignTimeViewModelLocator(); private static readonly SimpleContainer container; static DesignTimeViewModelLocator() {if (!Execute.InDesignMode) return;AssemblySource.Instance.Clear();AssemblySource.Instance.Add(typeof(DesignTimeViewModelLocator).Assembly);container = new SimpleContainer();IoC.GetInstance = container.GetInstance;IoC.GetAllInstances = container.GetAllInstances;IoC.BuildUp = container.BuildUp;var viewModels = typeof(DesignTimeViewModelLocator).Assembly.DefinedTypes .Where(t => t.IsSubclassOf(typeof(PropertyChangedBase)));foreach (var vm in viewModels){ container.RegisterPerRequest(vm.AsType(), null, vm.AsType());}container.Singleton<IEventAggregator, EventAggregator>();container.Singleton<ITrace, DesignTimeTrace>(); } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {// Blend creates types from a runtime/dynamic assembly, so match on name/namespacevar viewModelType = typeof(DesignTimeViewModelLocator).Assembly.DefinedTypes .First(t => t.Name == value.GetType().Name && value.GetType().Namespace.EndsWith(t.Namespace)).AsType();return container.GetInstance(viewModelType, null); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {throw new NotImplementedException(); } }} Use a static constructor to ensure this only runs once. Configure IoC/Dependency Injection by registering all the ViewModels. (In my project, they all derive from PropertyChangedBase.) Register the sample data you want to see in the designer. In this code, I’m using a DesignTimeTrace. Implement IValueConverterand return an instance of the desired ViewModel from the IoC container. DesignTimeViewModelLocator Benefits

This DesignTimeViewModelLocatortechnique combines the traditional ViewModelLocator approach with Dependency Injection to create arbitrarily complex ViewModels. It uses constructor injection to recursively create the necessary dependencies for any view. With Caliburn.Micro conventions, it will even bind and created nested child views. The XAML language service will also use the ViewModel type in the DesignInstanceattribute to provide accurate intellisense in data binding expressions.

I’d encourage you to try using a DesignTimeViewModelLocatorwith Caliburn.Micro in your next XAML project and see if it doesn’t speed up the development of your UI components.


1. Note that the blend designer process can be quite finicky. Ideally, you should make small incremental changes and keep it working from the start of a project. Otherwise, you can attach the debugger to XDesProc.exe and investigate the exceptions being thrown.↩