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

How To Embed An Application Into a Docking Library

0.00/5 (No votes)
3 Sep 2011 1  
Step by Step conversion of an application into a Docking application component
Introduction

Summary

AvalonDock is the most popular WPF open source docking library. Sofa wraps it, adds features and allows an easy integration of AvalonDock in many different architectures such as Prism.

In order to gain benefit from docking system and multi-instance features, an existing application must be converted into a Component that can be hosted in a docking Container.

This is fast and easy with Sofa: This article is a step by step description of this conversion.

Glossary of this Text

The Sofa.Container.SofaContainer and Sofa.Commons.SofaComponents are the main classes of Sofa.

The SofaContainer class is inserted in a WPF Window or a UserControl. The application managing this Window or this UserControl is named the Container in this documentation.

The SofaComponent class embeds a user's application. This application is named the Component in this documentation.

Example: In the following examples, we reuse the CSWPFMasterDetailBinding application. We transform it into a library and it becomes a Component. It will be run in the SofaBasiContainer application which is a Container.

1. From Application To Component

Step 1

The application is made of 2 projects:

1st project: Container: SofaBasicContainer

A Container has about nothing to do else ... containing. The SofaBasiContainer Container we use can be found in many examples and is reused with a small modification: Only adjust the MEF importing declaration:

  • The MEF [ImportMany] key in BaseWindow.xaml.cs is set to Sofa.Examples. SofaComponization.SofaBasicContainer_ViewContract.

2nd project: Component: CSWPFMasterDetailBinding

In this step, an application is transformed into a Component and is prepared for registration in the previous SofaBasicContainer Container.

The application is CSWPFMasterDetailBinding. It is coming from the All-In-One Code Framework (WPF) project (http://1code.codeplex.com).

  1. Transform the Application into a Library:
    • Project properties:
      • Output Type is changed from Windows Application to Class Library.
      • The library of this project must be reached by the SofaBasicContainer in order to use MEF: The build output path must be set to ..\SofaBasicContainer\bin\Debug\AddIns\
      • The target framework is set to .NET 4
    • App.xaml and App.xaml.cs are deleted. In some cases, the existing code must be moved to the Component's Usercontrol of the Container but there is none here.
    • MainWindow.xaml: The <Window x : Class ="CSWPFMasterDetailBinding.MainWindow" is changed into<UserControl x : Class... Related adjustments must be done to some tags (resources, triggers), properties (Title, events...) and partial class definition in the xaml and xaml.cs files.
  2. In order to become a Component, a library must only be able to register in a Container. This is done using MEF. The [Export] declarations and metadata are added in MainWindow.xaml.cs:
    • A reference to System.ComponentModel.Composition.Codeplex DLL must be added in order to use MEF. Sofa libraries are also referenced.
    • The MEF [Export] declarations are always the same and can be copied from any example. At this step, the only important data to edit are:
      • The [Export] key to adjust to the previous "Sofa.Examples. SofaComponization.SofaBasicContainer_ViewContract" key used in the [ImportMany] declaration of the Container.
      • The ProductName (i.e. SofaComponent id) set to "CSWPFMasterDetailBinding" and the Label.

That's it. You can run the SofaBasicContainer Container and find the CSWPFMasterDetailBinding Component in its menu. Load one or many, dock, undock, resize the windows...

Current result: The original application now runs in a Container and is a multi-instance application.

But its GUI has not changed...

2. From Simple Component to sub-Components

The goal of this step is to split the previous Component into 2 Components, each of them holding one of the lists of the previous Component.

The 2 Components can be hosted by the SofaBasicContainer but they are always working together and it is better to host them in a new Container, this Container being also a Component hosted in the SofaBasicContainer Container.

The " 1. Original Application To SofaComponent" folder is duplicated and renamed into "2. Simple SofaComponent to sub-SofaComponents".

1st project: Container: SofaBasicContainer

The SofaBasicContainer project is a standard Container and does not need any modification.

2nd project: Component & Container: CSWPFMasterDetailBinding

  1. The original Component is duplicated.
    • MainWindow.xaml class is duplicated. Files as well as classes are renamed: One is renamed "CustomerListComponent" and the other "OrderListComponent".
    • Only the Customer list (listViewCustomers) is kept in the CustomerListComponent and the Order list (listViewOrders) in OrderListComponent. The <UserControl.Resources> tag is related to CustomerList and is deleted from the OrderList.
    • The [Export] keys are adjusted: The new target of the MEF exports is the container that will be created in the next step.
      • Sofa.Examples.SofaComponization. SofaBasicContainer_ViewContract is renamed to Sofa.Examples.SofaComponization. MainComponent_ViewContract
      • ProductName are renamed from CSWPFMasterDetailBinding to CustomerList and OrderList and Labels are edited.
  2. New Component-Container

    As we want the 2 Components are opened in a unique window, we need a new Container. This Container will also be a Component hosted in the initial SofaBasicContainer in place of the previous MainWindow Component.

    This Container-Component is a new class named MainComponent. It is a mixture of the SofaBasicContainer (for Container code) and the previous MainWindow class (for Component code):

    Component

    • The only code of a Component is the MEF [Export] declaration. The key is set to the same value as in the [ImportMany] declaration of the SofaBasicContainer.

    Container

    • The current project hosts a Container : References to Sofa libraries must be added.
    • The class implements the IBaseContainer interface and must have the public SofaContainer SofaContainer (used) and public SofaMenu SofaMenu (not used) declarations.
    • The class will import the 2 previous "CustomerListComponent" and "OrderListComponent" Components. Its [ImportMany] declaration is set to the same value as the Component's [Export] keys. It implements the IPartImportsSatisfiedNotification interface in order the OnImportsSatisfied method is triggered. The Compose method of the SofaBasicContainer must be run once and is not present in this sub-Container.
    • The "sub-Components" of this Component-Container cannot be opened before the Container is fully initialized. The only valid time is when handling the "PostOpen" SofaCommonEvent in the SofaCommonEventHandler method. The sofaContainer.OpenComponent("CustomerList") and sofaContainer.OpenComponent("OrderList") methods are used. Component's names are hardcoded that is not elegant but not definitive (see 3. Binding the 2 lists).

    You can run the application and the 2 sub-Components will open in the MainComponent, itself opening in the SofaBasicContainer.

    But the OrderListComponent has no source for its binding and does not show the orders of the selected customer.

  3. Bind the 2 lists:

    Everything is done in the code of the PostOpen event handling of the MainComponent: The 2 components are reached using the Sofacontainer SofaComponentList and the listViewCustomers of the CustomerListComponent is used as DataContext for the OrderListComponent.

The application now works correctly.

3. About Perspectives ...

1st project: Container: SofaBasicContainer

This project implements a full perspective management scenario: Main features are load/save the last used perspective when opening/closing the Container and a menu allowing perspectives to be created, loaded and deleted.

Steps to implement this are:

  • Resources: The PerspectiveManager helper and its associated PerspectiveName are imported and namespaces adjusted.
  • Actions triggers: The previous OpenComponent call that loaded the CSWPFMasterDetailBinding Component in the BaseWindow constructor is deleted. It is replaced by a perspective management: The Window_Loaded and Window_Closed events are handled and respectively load and save the last used perspective.
  • Menus: Two menus are added; the first one allows basic functions such as Create/Delete perspectives as the second shows the list of existing perspectives and allows swapping from one to another. Related C# code is added in the BaseWindow to handle GUI events and forward them to the PerspectiveHelper class.

2nd project: Container & Component: CSWPFMasterDetailBinding

The two Components currently open as in a WPF Tab Control. This presentation is relevant for independent windows such as the MasterComponent instances but not when an action on one windows modifies the other one.

This project uses perspectives to load the 2 components side by side:

  • Resources: As for the SofaBasicContainer, the PerspectiveManager helper is imported.
  • Actions: The perspectiveHelper.LoadPerspective("MainComponent") method is added in the method handling the SofaCommon PostOpen event and not surprisingly the perspectiveHelper.SaveCurrentPerspective() in the " Closed" event.
  • The application is then run: The first time the LoadPerspective method is used, the perspective does not exist and this has no effect. Then the CSWPFMasterDetailBinding Component is loaded from the menu, the user organizes them as desired and the perspective is saved when closing the application by the Closed event handling. This creates the perspective that becomes a part of the application. The two sofaContainer.OpenComponent methods of the PostOpen event handling can then be deleted as the Components will automatically be opened with the perspective.

History

  • 2nd September, 2011: Initial version

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