Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sofa, AvalonDock and MEF

0.00/5 (No votes)
6 May 2011 1  
Sofa makes the link between AvalonDock and MEF, both using composable elements

Sofa New Release, AvalonDock and MEF

This article introduces the new Sofa release and impacts on using MEF:

  • Sofa can now be embedded in a project in a very light and easy way, without the parameters file.
  • New methods have been added and allow a better integration with MEF. Update starts at the "Sofa and MEF" paragraph.

Download Package

The download package is a Sofa enabled application. This application includes 2 components. The 1st one is the WebBrowser and the 2nd is the SofaExplorer used as example in this article. This application is one of the 4 that are provided with the full package available in the Sofa web site.

What is AvalonDock ?

AvalonDock is a WPF controls library which can be used to create a docking layout system like that is present in Visual Studio.
It supports fly-out panes, floating windows, multiple docking manager in same window, styles and themes and it can host WinForms controls.

See more at the AvalonDock web site.

What is Sofa?

Sofa wraps AvalonDock and provides:

  • Easier programming
  • Enhanced features
  • Additional features

One of those features are built-in functionalities that make Sofa MEF-compatible.

See more at the Sofa web site.

What Problems does MEF Solve?

  • MEF presents a simple solution for the runtime extensibility problem.
  • MEF provides a standard way for the host application to expose itself and consume external extensions.
  • MEF offers a set of discovery approaches for your application to locate and load available extensions.
  • MEF allows tagging extensions with additional metadata which facilitates rich querying and filtering.

See more at the MEF web site.

There is a symmetry between the docking layout of Sofa/Avalon and the MEF component architecture: MEF will allow integration of external components that will easily be represented as tabbed or split windows.

The MEF XFileExplorer Example

The example below is based on the MEF FileExplorer which can be found in the MEF preview download (MEF\MEF2_Preview2\Samples\CS\XFileExplorer) and has been "translated" into a set of Sofa components.

It is a basic file explorer with its treeview, its contents pane and 3 additional functionalities, a size pane showing a graphical representation of file sizes, a preview pane and favorites.
It is made of a main component named Shell and one "sub-component" for each functionality.

MEF Explorer_small.jpg

Once the application is translated into Sofa components and runs in the Sofa container, its appearance is not that different. You can find it as a Component of the SofaExplorer example in the download file.

Sofa Explorer_small.jpg

But the user can now customize it:

Another additional feature in the XFileExplorer is now a set of Sofa components and it becomes easy to manage many instances of each set using Sofa perspectives (top left menu).
A default filepath is associated to each perspective and allows managing filepaths favorites.

The main goal of this documentation is however not to demonstrate hosting an application as a Sofa component can easily provide additional functionalities: It is to show how MEF concepts and Sofa/AvalonDock concepts fits to each other and are easy to implement.

Micro MEF Tutorial

The way MEF works is very simple: MEF components, called Composable Parts, declare they are composable parts:

[Export("Microsoft.Samples.XFileExplorer.FileExplorerViewContract", typeof(UserControl))]
public partial class PreviewView : UserControl
{
    ...

Then, the host application registers composable parts using the CompositionContainer.ComposePart() method: In this example, all Composable Parts that are in the executing assembly will be registered:

private void Compose()
{
    var catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
}

The XFileExplorer example uses a different object, the CompositionBatch, but basically it is the same work.

At last, the hosting application can retrieve composable parts: For instance, in the XFileExplorer, the 2 following lines will make the _views object automatically loaded by MEF at run time.

[ImportMany("Microsoft.Samples.XFileExplorer.FileExplorerViewContract", 
AllowRecomposition=true)] 
private Lazy<UserControl, IFileExplorerViewMetadata>[] _views = null;

Sofa and MEF

When the MEF hosting application becomes a Sofa component, it is no longer an application but a UserControl. And it loses its App.xaml.cs file where the Compose method is usually stored.

This piece of code has to be moved to the main component. It is Shell.xaml.cs in our example.

Then Sofa components (i.e., MEF composable parts) must be loaded in AvalonDock:
The first step is to register them. The usual way is to use a CmpModel bean:

private void Button_Click(object sender, RoutedEventArgs e)
{
    CmpModel cmpModel = new CmpModel();
    cmpModel.UserControl = new BaseControl();

     ...
    cmpModel.Label = productName;
    cmpModel.ProductName = productName;

     sofaContainer.RegisterComponentByModel(cmpModel);
    ...
}

Using MEF is easier:

  • The Export statement of a Component:
    [Export("Sofa.Examples.SofaExplorer.XFileExplorer_ViewContract", 
    typeof(UserControl))]
    [ExportMetadata("ProductName", "Sofa_XFileExplorer_StatusView")]
    [ExportMetadata("AvalonContentType", "DockableContent")]
    [ExportMetadata("AvalonPaneGroup", "RightSide")]
    [ExportMetadata("IfExists_DefaultValue", "DoNotUseIt")]
    [ExportMetadata("ImageURI", "")]
    [ExportMetadata("Label", "Status")]
    [ExportMetadata("DockId", 0)]
    [ExportMetadata("Hidden", "False")]
    public partial class StatusView : UserControl 
    {
        ...
  • The import statement in the XFileExplorer:
    [ImportMany("Sofa.Examples.SofaExplorer.XFileExplorer_ViewContract", 
    AllowRecomposition = true)]
    private Lazy<UserControl, ISofaComponent_Metadata>[] _views = null;
  • The components registration:The RegisterComponent method accepts the Lazy<UserControl, ISofaComponent_Metadata> as parameter:
    public void OnImportSatisfied()
    {
        foreach (var view in _views)
        {
            shellSofacontainer.RegisterComponent(view);
        }
    }

At last, Components can be loaded as usual. There are 2 ways:

  • Open a perspective that only defines places and then open each Component that will find its place in the perspective
  • Open a perspective that defines places and components: Opening the perspective will automatically open components. This is the used way in the SofaExplorer.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here