[Caliburn Micro] How to use the same view for multiple view-models
By Michael DELVA on Thursday 30 September 2010, 16:32 - C# - Permalink
TweetThe default behavior of Caliburn Micro (CM), when it must locate a view from a view-model, is very straightforward: it takes the full name of the view-model, replaces the word "Model" by an empty string, and locates a view with this new name.
One problem with this way is that you must exactly have one view per view-model. So, if you have an abstract view-model, with multiple derived view-models, which only set some filters on the displayed data, you must have one view per derived VM. Which is kind of annoying, useless, and error prone.
In this short article, I'll show you how to easily implement a new feature which will allow us to specify, with an attribute on top of the VM, the view we want CM to display when we activate the VM.
To implement this behavior, we just have to replace the public static Func property LocateForModelType of the ViewLocator class.
In order to let the source files of CM intact, I will create a new static class ViewLocatorConfigurator, with a Configure method, which will "override" the default behavior of the LocateForModelType method. And to activate this custom behavior, I will just have to call this method in the Configure method of the BootStrapper.
But before I show you this class, let's create the attribute which will be used:
[DebuggerStepThrough]
[AttributeUsage(AttributeTargets.Class)]
public class UseViewAttribute : Attribute
{
private readonly string viewName;
public UseViewAttribute(string viewName)
{
Contract.Requires(!string.IsNullOrEmpty(viewName));
Contract.Ensures(ViewName != null);
this.viewName = viewName;
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(ViewName != null);
}
public string ViewName { [DebuggerStepThrough] get { return viewName; } }
}
Nothing fancy, we just have to specify the name of the view.
Now, let's see how to use this attribute in our new definition of LocateForModelType:
public static class ViewLocatorConfigurator
{
public static void Configure()
{
ViewLocator.LocateForModelType = (modelType, displayLocation, context) =>
{
var useViewAttributes = modelType.GetCustomAttributes(typeof(UseViewAttribute), true)
.Cast<UseViewAttribute>();
Contract.Assert(useViewAttributes.Count() <= 1, "There can only be zero or one UseViewAttribute on a view model");
string viewTypeName;
if (useViewAttributes.Count() == 1)
viewTypeName = string.Concat(modelType.Namespace.Replace("Model", string.Empty), ".", useViewAttributes.First().ViewName);
else
{
viewTypeName = modelType.FullName.Replace("Model", string.Empty);
if (context != null)
{
viewTypeName = viewTypeName.Remove(viewTypeName.Length - 4, 4);
viewTypeName = viewTypeName + "." + context;
}
}
var viewType = (from assembly in AssemblySource.Instance
from type in assembly.GetExportedTypes()
where type.FullName == viewTypeName
select type).FirstOrDefault();
return viewType == null
? new TextBlock {Text = string.Format("{0} not found.", viewTypeName)}
: ViewLocator.GetOrCreateViewType(viewType);
};
}
}
I don't think this needs a lot of explanations. I just check there is only zero or one UseViewAttribute on top of the VM, and I create the full name of the view, based on the namespace of the VM and the name of the view given in parameter to the attribute. And I use the same code as the original version to create the view.
This was a short article, but I hope you enjoyed it, and that it will convince you of the power of Caliburn Micro.
See you soon!
Comments
I did the same thing 8 months ago. Maybe we should collaborate more since it was your talk at PDC that made me change my design for my MVVM framework to be Convention over Configuration.
In fact, I used a strategy Pattern to find the View with 3 built in strategies FindViewByAttribute(), FindViewByConvention(), and FindViewByMef().
And all conventions used are discoverable via MEF and new ones can be added at anytime.
Thank again for giving that talk at PDC. It was the most eye opening session I have been in since I started going to conventions back in the early 90s.
Opps. Thought this was written by Rob Eisenberg. Ignore my last comment.