How To Embed An Application Into a Docking Library
Step by Step conversion of an application into a Docking application component

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

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 toSofa.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).
- 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.
- Project properties:
- 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 theLabel
.
- The
- A reference to
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
- 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 theCustomerListComponent
and theOrder
list (listViewOrders
) inOrderListComponent
. The<UserControl.Resources>
tag is related toCustomerList
and is deleted from theOrderList
. - 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 toSofa.Examples.SofaComponization
.MainComponent_ViewContract
ProductName
are renamed fromCSWPFMasterD
toetailBinding
CustomerList
andOrderList
andLabels
are edited.
- MainWindow.xaml class is duplicated. Files as well as classes are renamed: One is renamed "
- 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 previousMainWindow
Component.This Container-Component is a new class named
MainComponent
. It is a mixture of theSofaBasicContainer
(for Container code) and the previousMainWindow
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 theSofaBasicContainer
.
Container
- The current project hosts a Container : References to
Sofa
libraries must be added. - The class implements the
IBaseContainer
interface and must have thepublic SofaContainer SofaContainer
(used) andpublic 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 theIPartImportsSatisfiedNotification
interface in order theOnImportsSatisfied
method is triggered. TheCompose
method of theSofaBasicContainer
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 theSofaCommonEventHandler
method. ThesofaContainer.OpenComponent("CustomerList")
andsofaContainer.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 theSofaBasicContainer
.But the
OrderListComponent
has no source for its binding and does not show the orders of the selected customer. - The only code of a Component is the MEF
- Bind the 2 lists:
Everything is done in the code of the
PostOpen
event handling of theMainComponent
: The 2 components are reached using theSofacontainer SofaComponentList
and thelistViewCustomers
of theCustomerListComponent
is used asDataContext
for theOrderListComponent
.
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 associatedPerspectiveName
are imported and namespaces adjusted. - Actions triggers: The previous
OpenComponent
call that loaded theCSWPFMasterDetailBinding
Component in theBaseWindow
constructor is deleted. It is replaced by a perspective management: TheWindow_Loaded
andWindow_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 theBaseWindow
to handle GUI events and forward them to thePerspectiveHelper
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
, thePerspectiveManager
helper is imported. - Actions: The
perspectiveHelper.LoadPerspective("
method is added in the method handling theMainComponent
")SofaCommon PostOpen
event and not surprisingly theperspectiveHelper.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 theCSWPFMasterDetailBinding
Component is loaded from the menu, the user organizes them as desired and the perspective is saved when closing the application by theClosed
event handling. This creates the perspective that becomes a part of the application. The twosofaContainer.OpenComponent
methods of thePostOpen
event handling can then be deleted as the Components will automatically be opened with the perspective.
History
- 2nd September, 2011: Initial version