Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια

Παρουσίαση με Ετικέτες

Όλες οι Ετικέτε... » WPF   (RSS)

An Extensible WPF client layout with RibbonTab submenus using MEF

I was just experimenting with extensibility using MEF (MEF is a .NET resident for .NET4 and SL4), when I created a small proof of this functionality that I could share via my blog. It is a semi-patterned (only for the main shell, my shell, not a PRISM one with viewregions and stuff, just an MVVM main area implemented with MVVMLight), but it does what it is supposed to do. So what is is supposed to do? It is an application, which can be extended by simply, drag and dropping, dlls in the Plugins directory. Every plugin, contains a certain amount, of RibbonTabs and each one, can provide applications, via RibbonTabItems (utilized the Microsoft Ribbon for WPF October 2010). By clicking each one of these items, one can open the corresponding application in the closable TabControl next to the plugin option menu. This menu, by the way is a WPF Outlook bar reused by this codeproject. The Favorites menu is a subset of the Applications option. The plugin system is based on MEF, demonstrating a simple plugin system.

Simple View

image

Extended View

image

So, this application consists of a simple ViewModel which contains all the necessary properties and a simple View, databound to this ViewModel. The most important Bindings are the one of the SelectedPlugin.RibbonTabCollection and the OpenedApplications of the TabControl. Upon selection of a certain tabitem, we switch to the corresponding plugin too (containing the application of the selected item).  Applications are some usercontrols (with codebehind), I constructed randomly and exposed as *concrete* implementations via the plugin mechanism. So, how does this work?

Managed Extensibility Framework

I use two main interfaces, one for Applications…

[code]
public interface IApplication
{
    String ApplicationName { get; set; }
    ContentControl ApplicationView { get; set; }
}[/code]

…and one for plugins.  The interface IPlugin, is marked as InheritedExport (a MEF attribute), mentioning that whoever implements this, can be discovered by MEF and used at some way (discribed below)!

[code][InheritedExport]
public interface IPlugin
{
    ObservableCollection<RibbonTab> RibbonTabCollection { get; }
    ObservableCollection<IApplication> ApplicationCollection { get; }
}

public interface IPluginMetadata
{
    string Name { get; }
    string Version { get; }
}

[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class PluginMetadataAttribute : ExportAttribute
{
    public PluginMetadataAttribute(string name, string version)
        : base(typeof(IPlugin))
    {
        Name = name;
        Version = version;
    }

    public string Name { get; set; }
    public string Version { get; set; }
}[/code]

So our plugin has a RibbonTabcollection (again constructing via XAML), and an ApplicationCollection. It also has metadata like the name. This string, is the one, databound in the outlookbar list, providing for the name of the selection via a data template.

[code]<DataTemplate x:Key="PluginItemTemplate">
    <TextBlock Text="{Binding Metadata.Name}" />
</DataTemplate>[/code]

Messaging between views is implemented via a simple Messaging schema, using the provided mechanism of MVVMLight.

The important thing is the AllPlugins property on the ViewModel. It is declared as an observable collection of lazy items, that are constructed upon dereferencing the “Value” property of each Lazy (of IPlugin) instance. How is this dereference achieved? It is achieved for free, by the data binding mechanism. Notice the SelectedValue and SelectedValuePath, pair Smile.

[code]<ListBox ItemsSource="{Binding AllPlugins}"
                         SelectionMode="Single"
                         ItemTemplate="{StaticResource PluginItemTemplate}"
                         SelectedValue="{Binding SelectedPlugin, Mode=TwoWay}"
                         SelectedValuePath="Value" />[/code]

Notice the ImportMany attribute enabling the collection to be populated by the MEF mechanism.

[code][ImportMany]
private ObservableCollection<Lazy<IPlugin, IPluginMetadata>> allPlugins
    = new ObservableCollection<Lazy<IPlugin, IPluginMetadata>>();
public ObservableCollection<Lazy<IPlugin, IPluginMetadata>> AllPlugins
{
    get
    {
        return allPlugins;
    }
    set
    {
        allPlugins = value;
        RaisePropertyChanged("AllPlugins");
    }
}[/code]

Final major question! How are plugins discovered? I have marked a directory on my system as Plugins (via App.config – change that when you run the demo). The catalog, that is going to discover all these types (the ones that are dropped there) is a DirectoryCatalog which can compose all the necessary types.

[code]DirectoryCatalog directoryCatalog =

new DirectoryCatalog(ConfigurationManager.AppSettings["PluginsDirectory"]);

container = new CompositionContainer(directoryCatalog);

//Fill the imports of this object
try
{
    this.container.ComposeParts(this);
}
catch (CompositionException compositionException)
{
    Debug.WriteLine(compositionException.ToString());
}[/code]

At the demo provided you will see that there are two projects, referencing the contract and implementing as needed.

image

This is only, one simple use of MEF. Future work on this demo, could be to provide dynamic discovery of ViewModel and View types, leaving to MEF the instantiation of them, leaving MEF the inversion of control responsibility too!

I hope you enjoyed this first little tutorial for MEF. The same principles can be applied to Silverlight application for discovering XAP’s for example.

Download the demo

Posted: Τετάρτη, 1 Δεκεμβρίου 2010 1:50 πμ από George J. Capnias | 0 σχόλια
Δημοσίευση στην κατηγορία: , , ,