WPF + MEF = Declarative Composite UI

August 11, 2009

5 comments

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:



  1. Placeholders (panels) created in XAML have to be exposed to and interact by the code behind

  2. Each window (shell) has to have specific logic for loading the imported views into placeholders

  3. 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.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

5 comments

  1. Glenn BlockAugust 29, 2009 ב 23:03

    Tomer this is great! We have been prototyping for a while around an Import/ImportMany markup extension. One thing I didn’t like about my prototypes was they required passing a string for the contract. I totally forgot about x:Type, thanks for reminding me. This is a much cleaner approach. When we do our XAML work for MEF vNext, we will definitely keep this in mind.

    You rock!
    Glenn

    Reply
  2. Glenn BlockAugust 29, 2009 ב 23:14

    BTW, the my XAML integration spike using MarkupExtensions is available here….http://cid-f8b2fd72406fb218.skydrive.live.com/self.aspx/blog/WPF%20Composition.zip

    Reply
  3. Tomer ShamamAugust 29, 2009 ב 23:18

    Hi Glenn,

    I’m glad that I could help, and I’m waiting to see your work around MEF and XAML in vNext.

    Nice to hear from you!

    Tomer

    Reply
  4. Glenn BlockAugust 31, 2009 ב 00:13

    Hi Tomer

    Looks like my last comment was lost. You many want to consider using a MarkupExtension instead. Using an ME will allow you to specify specific DependencyProperties on any control, rather than having a default one tied to the Import attached prop.

    For example you could do this

    Regards
    Glenn

    Reply
  5. Tomer ShamamSeptember 1, 2009 ב 08:46

    Yes you right, it’s a better approach.
    I left a message regards this issue and how to extend it in your live space.

    Thanks

    Reply