Injecting Views into properties with a View-Model-First approach

While trying to refactor the current application I am working on, I had some problems with a third party control that is not so MVVM-firendly. In particular, the current RibbonWindow implementation I am using requires that a Ribbon control and a StatusBar
control are directly set as depedency properties. Unfortunally, the standard Caliburn.Micro view-model-first approach (using View.Model to inject a view into a ContentControl) is not applicabile to this scenario, since such controls are not ContentControls
and there is no way to define a template for them.

This issue led me to develop an alternative to the View.Model approach, that can be used in this kind of scenario or wherever a ContentControl cannot be used as the container of the injected view (e.g. with a ContextMenu).

A quick sample of this approach is shown below (taken from the attached sample project):

In other words, it is a generalization of the Caliburn.Micro implementation, that can be used to overcome the limitations of controls that were not designed to be MVVM-friendly.

The source code of the extension is shown below:

namespace Caliburn.Micro.Extensions
{
#region Namespaces
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
#endregion///<summary>/// Class used to define the injection of a view (retrieved using a view-model) into a property of another view.///</summary>
[ContentProperty("Model")]
publicclass Inject : MultiBinding, IAddChild
{
#region Properties
///<summary>/// Gets the converter used to extract the view to be injected, using the specified view-model.///</summary>///<value>The converter used to extract the view to be injected, using the specified view-model.</value>publicnew IMultiValueConverter Converter
{
get { returnbase.Converter; }
}
///<summary>/// Gets the optional parameter passed to the converter as additional information.///</summary>///<value>The optional parameter passed to the converter as additional information.</value>publicnewobject ConverterParameter
{
get { returnbase.ConverterParameter; }
}
///<summary>/// Gets or sets a value that indicates the direction of the data flow of this binding.///</summary>///<value>The direction of the data flow of this binding.</value>publicnew BindingMode Mode
{
get { returnbase.Mode; }
set
{
if (value != BindingMode.OneWay && value != BindingMode.OneTime)
thrownew InvalidOperationException("An inject can only be defined as OneWay or OneTime");
}
}
///<summary>/// Gets or sets the context.///</summary>///<value>The context.</value>publicobject Context
{
get { return ((ViewModelToViewMultiConverterParameter)base.ConverterParameter).Context; }
set { ((ViewModelToViewMultiConverterParameter)base.ConverterParameter).Context = value; }
}
///<summary>/// Gets the collection of <see cref = "T:System.Windows.Data.Binding" /> objects within this <see cref = "Inject" /> instance.///</summary>///<value></value>///<returns>A collection of <see cref = "T:System.Windows.Data.Binding" /> objects.</returns>publicnew ReadOnlyCollection<BindingBase> Bindings
{
get { returnnew ReadOnlyCollection<BindingBase>(base.Bindings); }
}
///<summary>/// Gets or sets the model used to inject the view.///</summary>///<value>The model used to inject the view.</value>public BindingBase Model
{
get { returnbase.Bindings.FirstOrDefault(); }
set
{
Collection<BindingBase> collection = base.Bindings;
if (collection.Count > 0)
collection[0] = value;
else
collection.Add(value);
}
}
#endregion///<summary>/// Initializes a new instance of the <see cref = "Inject" /> class.///</summary>public Inject()
{
base.Mode = BindingMode.OneWay;
base.Converter = ViewModelToViewMultiConverter.Default;
base.ConverterParameter = new ViewModelToViewMultiConverterParameter();
}
#region IAddChild Members
///<summary>/// Adds a child object.///</summary>///<param name = "value">The child object to add.</param>void IAddChild.AddChild(object value)
{
thrownew NotSupportedException("Direct content is not supported");
}
///<summary>/// Adds the text content of a node to the object.///</summary>///<param name = "text">The text to add to the object.</param>void IAddChild.AddText(string text)
{
thrownew NotSupportedException("Direct content is not supported");
}
#endregion#region Nested type: ViewModelToViewMultiConverter
///<summary>/// Class used to define the multi-converter used to retrieve a view using a view-model.///</summary>privateclass ViewModelToViewMultiConverter : IMultiValueConverter
{
#region Static Properties
///<summary>/// Gets the default instance of the class.///</summary>///<value>The the default instance of the class.</value>publicstatic ViewModelToViewMultiConverter Default
{
get { return Instancer._Instance; }
}
#endregion#region IMultiValueConverter Members
///<summary>/// Converts source values to a value for the binding target. The data binding engine calls this method when it propagates the values from source bindings to the binding target.///</summary>///<param name = "values">The array of values that the source bindings in the <see cref = "T:System.Windows.Data.MultiBinding" /> produces. The value <see cref = "F:System.Windows.DependencyProperty.UnsetValue" /> indicates that the source binding has no value to provide for conversion.</param>///<param name = "targetType">The type of the binding target property.</param>///<param name = "parameter">The converter parameter to use.</param>///<param name = "culture">The culture to use in the converter.</param>///<returns>/// A converted value.If the method returns null, the valid null value is used.A return value of <see cref = "T:System.Windows.DependencyProperty" />.<see cref = "F:System.Windows.DependencyProperty.UnsetValue" /> indicates that the converter did not produce a value, and that the binding will use the <see cref = "P:System.Windows.Data.BindingBase.FallbackValue" /> if it is available, or else will use the default value.A return value of <see cref = "T:System.Windows.Data.Binding" />.<see cref = "F:System.Windows.Data.Binding.DoNothing" /> indicates that the binding does not transfer the value or use the <see cref = "P:System.Windows.Data.BindingBase.FallbackValue" /> or the default value.///</returns>publicobject Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var param = (ViewModelToViewMultiConverterParameter)parameter;
UIElement view = null;
object viewModel = values.FirstOrDefault();
if (viewModel != null)
{
view = ViewLocator.LocateForModel(viewModel, null, param.Context);
ViewModelBinder.Bind(viewModel, view, param.Context);
}
return view;
}
///<summary>/// Converts a binding target value to the source binding values.///</summary>///<param name = "value">The value that the binding target produces.</param>///<param name = "targetTypes">The array of types to convert to. The array length indicates the number and types of values that are suggested for the method to return.</param>///<param name = "parameter">The converter parameter to use.</param>///<param name = "culture">The culture to use in the converter.</param>///<returns>/// An array of values that have been converted from the target value back to the source values.///</returns>publicobject[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
thrownew NotSupportedException();
}
#endregion#region Nested type: Instancer
privatestaticclass Instancer
{
#region Static Fields
internalstaticreadonly ViewModelToViewMultiConverter _Instance = new ViewModelToViewMultiConverter();
#endregion
}
#endregion
}
#endregion#region Nested type: ViewModelToViewMultiConverterParameter
///<summary>/// Class used to define the parameters used by the converter that extract a view from a view-model.///</summary>privateclass ViewModelToViewMultiConverterParameter
{
#region Properties
///<summary>/// Gets or sets the context.///</summary>///<value>The context.</value>publicobject Context { get; set; }
#endregion
}
#endregion
}
}

As you can see, the underlying implementation of the extension is a MultiBinding; this implementation is necessary to overcome some limitations in the .NET framework extensibility, such as:

the fact that the only ways to inherit the databinding context is either inherit from Freezable or from BindingBase

even if inherited, BindingBase does not allow for customization, due to the fact that the ProvideValue function is sealed

Freezable objects are not MarkupExtensions and cannot be used to produce a value to inject

the Binding class has many specific properties and risks to make the extension confusing for the user

Moreover, using a MultiBinding allows for a better extensibility, allowing for multiple values to be passed as bindings (e.g. a fallback view-model if the required one is not found).

The only issue with the Inject extension is that, since it is a BindingBase, it cannot be used on plain CLR properties, but just DependencyProperties; the issue is pretty much non-existent, since WPF/Silverlight controls mostrly avoid to declare and use
plain CLR properties, nevertheless I implemented another solution that has not this limitation and avoids to use reflection. In short, I created an attached property that can be used to target individual properties of a view (defined using a PropertyPath),
and inject a sub-view retrieved using a specified view-model... if you are interested in this solution, you can check the code available in the link below, and I'll be willing to answer your questions (if any)... an example of the usage of such approach is
the following one: