如何嵌入一个应用程序到停靠库
逐步将一个应用程序转换为停靠应用程序组件

摘要
AvalonDock 是最流行的 WPF 开源停靠库。 Sofa 对其进行了封装,增加了功能,并允许 AvalonDock 轻松集成到 Prism 等多种不同架构中。
为了从停靠系统和多实例功能中获益,必须将现有应用程序转换为可托管在停靠容器中的组件。
使用 Sofa 可以快速轻松地完成此操作:本文档将逐步介绍此转换过程。
本文术语表
Sofa.Container.SofaContainer
和 Sofa.Commons.SofaComponents
是 Sofa
的主要类。
SofaContainer
类被插入到 WPF 窗口或 UserControl
中。管理此窗口或 UserControl
的应用程序在此文档中称为容器。
SofaComponent
类嵌入了用户的应用程序。此应用程序在此文档中称为组件。
示例:在以下示例中,我们将重用 CSWPFMasterDetailBinding
应用程序。我们将其转换为库,并使其成为组件。它将在 SofaBasiContainer
应用程序中运行,该应用程序是一个容器。
1. 从应用程序到组件

该应用程序由 2 个项目组成
第 1 个项目:容器: SofaBasicContainer
容器基本上只需要“包含”功能。我们使用的 SofaBasiContainer
容器在许多示例中都可以找到,并且被重用,只需稍作修改:只需调整 MEF 导入声明即可。
- BaseWindow.xaml.cs 中的 MEF
[ImportMany]
键设置为Sofa.Examples. SofaComponization.SofaBasicContainer_ViewContract
。
第 2 个项目:组件: CSWPFMasterDetailBinding
在此步骤中,应用程序被转换为组件,并准备在之前的 SofaBasicContainer
容器中注册。
该应用程序是 CSWPFMasterDetailBinding
。它来自 All-In-One Code Framework (WPF) 项目(http://1code.codeplex.com)。
- 将应用程序转换为库
- 项目属性
- 输出类型从 Windows 应用程序更改为类库。
SofaBasicContainer
为了使用 MEF,必须能够访问此项目的库:必须将生成输出路径设置为..\SofaBasicContainer\bin\Debug\AddIns\- 目标框架设置为 .NET 4
- 删除 App.xaml 和 App.xaml.cs。在某些情况下,现有代码必须移至容器的组件的
Usercontrol
,但此处没有。 - MainWindow.xaml:
<Window x : Class ="CSWPFMasterDetailBinding.MainWindow"
已更改为<UserControl x : Class...
在xaml和xaml.cs文件中,必须对某些标签(资源、触发器)、属性(Title、事件...)和部分类定义进行相应的调整。
- 项目属性
- 为了成为组件,库必须能够注册到容器。这是使用 MEF 完成的。在 MainWindow.xaml.cs 中添加了
[Export]
声明和元数据。- 必须添加对
System.ComponentModel.Composition.Codeplex
DLL 的引用才能使用 MEF。还需要引用Sofa
库。 - MEF
[Export]
声明始终相同,可以从任何示例中复制。在此步骤中,唯一重要的要编辑的数据是- 需要调整
[Export]
键以匹配容器中[ImportMany]
声明中使用的“Sofa.Examples. SofaComponization.SofaBasicContainer_ViewContract
”键。 ProductName
(即SofaComponent id
)设置为“CSWPFMasterDetailBinding
”,并设置Label
。
- 需要调整
- 必须添加对
就是这样。您可以运行 SofaBasicContainer
容器,并在其菜单中找到 CSWPFMasterDetailBinding
组件。加载一个或多个,停靠,取消停靠,调整窗口大小……
当前结果:原始应用程序现在在容器中运行,并且是多实例应用程序。
但其 GUI 没有改变……
2. 从简单组件到子组件

此步骤的目标是将之前的组件拆分为 2 个组件,每个组件包含之前组件中的一个列表。
这两个组件可以由 SofaBasicContainer
托管,但它们总是协同工作,因此最好将它们托管在一个新容器中,而这个容器本身也是一个组件,托管在 SofaBasicContainer
容器中。
“1. 原始应用程序到 SofaComponent”文件夹被复制并重命名为“2. 简单 SofaComponent 到子 SofaComponents”。
第 1 个项目:容器: SofaBasicContainer
SofaBasicContainer
项目是一个标准的容器,不需要任何修改。
第 2 个项目:组件和容器:CSWPFMasterDetailBinding
- 原始组件被复制。
- MainWindow.xaml 类被复制。文件和类被重命名:一个重命名为“
CustomerListComponent
”,另一个重命名为“OrderListComponent
”。 - 在
CustomerListComponent
中仅保留Customer
列表(listViewCustomers
),在OrderListComponent
中保留Order
列表(listViewOrders
)。<UserControl.Resources>
标签与CustomerList
相关,并从OrderList
中删除。 - 组件的 MEF 导出目标已更改为下一步将创建的容器。
Sofa.Examples.SofaComponization
.SofaBasicContainer_ViewContract
已重命名为Sofa.Examples.SofaComponization
.MainComponent_ViewContract
ProductName
已从CSWPFMasterD
重命名为etailBinding
CustomerList
和OrderList
,并编辑了Labels
。
- MainWindow.xaml 类被复制。文件和类被重命名:一个重命名为“
- 新的组件-容器
由于我们希望这两个组件在一个窗口中打开,因此我们需要一个新容器。这个容器也将是一个组件,托管在初始
SofaBasicContainer
中,取代之前的MainWindow
组件。这个容器-组件是一个名为
MainComponent
的新类。它是SofaBasicContainer
(用于容器代码)和之前的MainWindow
类(用于组件代码)的混合体。组件 (Component)
- 组件的唯一代码是 MEF
[Export]
声明。键设置为与SofaBasicContainer
的[ImportMany]
声明相同的值。
容器
- 当前项目托管一个容器:必须添加对
Sofa
库的引用。 - 该类实现
IBaseContainer
接口,并且必须具有public SofaContainer SofaContainer
(已使用)和public SofaMenu SofaMenu
(未使用)声明。 - 该类将导入之前的两个“
CustomerListComponent
”和“OrderListComponent
”组件。其[ImportMany]
声明设置为与组件的[Export]
键相同的值。它实现IPartImportsSatisfiedNotification
接口,以便触发OnImportsSatisfied
方法。SofaBasicContainer
的Compose
方法必须运行一次,并且在此子容器中不存在。 - 此组件-容器的“子组件”在容器完全初始化之前无法打开。唯一有效的时间是在
SofaCommonEventHandler
方法中处理“PostOpen
”SofaCommonEvent
时。使用了sofaContainer.OpenComponent("CustomerList")
和sofaContainer.OpenComponent("OrderList")
方法。组件名称是硬编码的,这不优雅但不是最终的(参见 3. 绑定两个列表)。
您可以运行应用程序,这两个子组件将打开在
MainComponent
中,而MainComponent
又打开在SofaBasicContainer
中。但是
OrderListComponent
没有绑定源,也没有显示所选客户的订单。 - 组件的唯一代码是 MEF
- 绑定两个列表
所有操作都在
MainComponent
的PostOpen
事件处理代码中完成:使用Sofacontainer SofaComponentList
访问这两个组件,并将CustomerListComponent
的listViewCustomers
用作OrderListComponent
的DataContext
。
现在应用程序可以正常工作了。
3. 关于视图...
第 1 个项目:容器: SofaBasicContainer
此项目实现了一个完整的视图管理场景:主要功能包括在打开/关闭容器时加载/保存上次使用的视图,以及一个允许创建、加载和删除视图的菜单。
实现此目标所需的步骤如下:
- 资源:导入
PerspectiveManager
助手及其关联的PerspectiveName
,并调整命名空间。 - 操作触发器:删除了之前在
BaseWindow
构造函数中调用OpenComponent
以加载CSWPFMasterDetailBinding
组件的代码。取而代之的是视图管理:处理Window_Loaded
和Window_Closed
事件,分别加载和保存上次使用的视图。 - 菜单:添加了两个菜单;第一个菜单允许进行基本功能,如
Create
/Delete
视图,第二个菜单显示现有视图列表并允许在它们之间切换。在BaseWindow
中添加了相关的 C# 代码来处理 GUI 事件并将其转发给PerspectiveHelper
类。
第 2 个项目:容器和组件:CSWPFMasterDetailBinding
目前打开的两个组件就像在 WPF Tab 控件中一样。这种呈现方式对于独立窗口(如 MasterComponent
实例)是相关的,但当一个窗口上的操作会修改另一个窗口时则不适用。
此项目使用视图来并排加载两个组件。
- 资源:与
SofaBasicContainer
一样,导入PerspectiveManager
助手。 - 操作:在处理
SofaCommon PostOpen
事件的方法中添加了perspectiveHelper.LoadPerspective("
方法,而在“MainComponent
")Closed
”事件中则添加了perspectiveHelper.SaveCurrentPerspective()
。 - 然后运行应用程序:第一次使用
LoadPerspective
方法时,视图不存在,因此没有效果。然后从菜单中加载CSWPFMasterDetailBinding
组件,用户根据需要进行组织,并在关闭应用程序时通过Closed
事件处理保存视图。这会创建视图,该视图成为应用程序的一部分。然后可以删除PostOpen
事件处理中的两个sofaContainer.OpenComponent
方法,因为组件将自动随视图一起打开。
历史
- 2011 年 9 月 2 日:初始版本