I’ve been playing around with MEF lately and I have to say that it has a great potential for building Composite UIs.
One of the sample applications arrived with MEF Preview 6 (called MEFLook), demonstrates how to implement kind of Outlook composite application based on WPF. The interesting part of this application is that views can be imported into the main window as dynamic parts, simply by decorating them with the MEF Export attribute.
<Window x:Class="MeflookSample.MeflookShell" ...>
<DockPanel Name="dockPanel">
...
</DockPanel>
</Window>
[Export("MainWindow", typeof(Window))]
public partial class MeflookShell : System.Windows.Window
{
[ImportMany("MeflookView")]
private Lazy<UserControl, IShellViewMetadata>[] views = null;
private void OnLoaded(object sender, RoutedEventArgs e)
{
foreach (var view in views.OrderBy(i => i.Metadata.Index))
{
dockPanel.Children.Add(view.Value);
}
}
}
In the proposed solution, each window has to load its own dependencies (view/parts) by using the ImportManyAttribute attribute and then add them manually to the correct panel or placeholder.
This method has several disadvantages:
- Placeholders (panels) created in XAML have to be exposed to and interact by the code behind
- Each window (shell) has to have specific logic for loading the imported views into placeholders
- Presenters (MVVM) are not supported since panels do not support non UIElement types
To overcome these disadvantages I’ve developed a technique called “Declarative Import”. This technique provides an option to import views/presenters via XAML based on attached properties.
<Window x:Class="ImportFromXaml.Parts.Shell.PlayerShell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mef="clr-namespace:ImportFromXaml"
xmlns:c="clr-namespace:ImportFromXaml.Parts.Player">
<DockPanel>
<ContentControl mef:CompositionServices.Import="{x:Type c:IRibbonControl}"
DockPanel.Dock="Top" />
<Expander Header="Parts" IsExpanded="True" DockPanel.Dock="Bottom" Height="200">
<TabControl mef:CompositionServices.Import="{x:Type c:IPlayerPart}"
Height="200">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding DisplayHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
</Expander>
<ContentControl mef:CompositionServices.Import="{x:Type c:IPlayerControl}"
DockPanel.Dock="Bottom"
Height="100" />
</DockPanel>
</Window>
As you can see, the CompositionServices.Import is an attached property that has at least one parameter: Contract Type. Attaching this property to supported elements automatically imports one or many exports, and add them to the target. Views can be either UIElement or any CLR type such as Presenter.
Lets take look at the CompositionServices type:
public static class CompositionServices
{
private static CompositionContainer Container { ... }
public static Type GetImport(DependencyObject obj)
{
return (Type)obj.GetValue(ImportProperty);
}
public static void SetImport(DependencyObject obj, Type value)
{
obj.SetValue(ImportProperty, value);
}
public static readonly DependencyProperty ImportProperty =
DependencyProperty.RegisterAttached(
"Import",
typeof(Type),
typeof(CompositionServices), new UIPropertyMetadata(null, ImportPropertyChanged));
private static void ImportPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var adapters = Container.GetExportedValues<IViewAggregatorAdapter>();
var adapter = adapters.FirstOrDefault(a => a.CanAdapt(d));
if (adapter == null)
{
return;
}
adapter.Adapt(d);
var views = Container.GetExportedValues(e.NewValue as Type);
adapter.AddViews(views);
adapter.Release();
}
#endregion
}
Whenever Import attached property is attached to an element, the ImportPropertyChanged method is activated and does the following:
1. Export view aggregator adapters by using the IViewAggregatorAdapter. A view aggregator adapter is a simple adapter that knows how to add view/s into the target element. I’ve created two adapters, one for the ContentControl and the other for ItemsControl.
2. Extract the first adapter that knows how to deal with the target.
3. Call an extension method that get all exports match to the given contract type.
4. Use the adapter to add all export values into the target.
Now that we can import parts from XAML we can create kind of Composite UI declaratively. Of course there is a lot of work to do making this a production tool.
So I hope that Prism V-Next will use MEF in its guts. This solution is only a simple thought.
You can download the solution from here.