可停靠的 CAB 工作区






3.89/5 (26投票s)
使用 DivElements SandDock 控件的 CompositeUI Application Block 的自定义工作区
必备组件
本文涵盖的代码使用了 DivElements 的专有控件套件。您必须购买 DivElements 产品许可证或下载 30 天试用版才能运行代码。您可以在 http://www.divelements.com/ 下载 SandDock。
引言
复合 UI 应用程序块 (CAB) 是 Microsoft 最新推出的产品,旨在帮助开发人员创建可扩展、模块化、可插拔的应用程序。如果您不熟悉 CAB,我强烈建议您停止阅读本文,然后前往 MSDN 阅读一些可用的文档。还有一些动手实验将帮助您了解 CAB 的全部内容。
这个应用程序块提供了许多功能。我花了过去三周的时间研究它,才刚刚开始将这些部分联系起来。本文涵盖的部分是工作区。下面的图形显示了我的 DockableWindowWorkspace
和 TabbedDocumentWorkspace
的实际运行效果。
![[Docking Hints]](https://cloudfront.codeproject.com/smart/dockablecabworkspaces/dockinghints.gif)
工作区傻瓜指南
我不敢声称完全理解复合 UI 应用程序块背后的概念,但这是我用一分钟时间总结的工作区是什么以及如何使用它们。Workspace
作为 WorkItems
公开的 SmartPart
视图的容器。CAB 附带了几个内置的 Workspace
,用于在 Windows、TabControls、Zones 和 Decks 中显示 SmartPart
。WindowWorkspace
在其自己的窗口中显示每个 SmartPart
。TabWorkspace
在其自己的 TabPage
中显示每个 SmartPart
。如果您想知道 SmartPart
和 WorkItem
是什么,那很好地说明了您在阅读本文的其余部分之前需要完成 CAB 的动手实验。
应用程序块的设计允许开发人员在不破坏应用程序的情况下更改 Workspace
。这就引出了“Shell”应用程序的概念。Shell 应用程序提供一个或多个 Workspace
,并允许动态加载的模块在这些 Workspace
中显示 WorkItem
。可以修改 Shell 应用程序来完全改变用户与 WorkItem
交互的方式。我将通过修改其中一个动手实验样本来使用我的自定义 Workspace
来演示这个概念。
Microsoft 使创建自定义 Workspace
变得非常简单直接。其中一项动手实验将引导您使用 TreeView
控件创建自定义 Workspace
。我的自定义工作区是基于 CAB 源代码下载中包含的 WindowWorkspace
创建的。
工作区设计
DockableWindowWorkspace
和 TabbedDocumentWorkspace
都必须使用现有的 SandDockManager
进行构造。使用过 DivElements 的 SandDock 控件的您将熟悉 SandDockManager
。SandDock
控件允许您创建一个类似于 Visual Studio 提供的界面,包括可固定/可折叠的窗口,用户可以重新排列它们。请参阅下面的屏幕截图以了解此类 UI 的示例。
![[Sidebar on Left]](https://cloudfront.codeproject.com/smart/dockablecabworkspaces/left.gif)
SandDockManager
是一个必须添加到您的 Shell 应用程序窗体的组件。下面来自 BankShellForm.cs 的代码显示了自定义 Workspace
的创建,将之前添加到窗体的 SandDockManager
传递过去。
[InjectionConstructor]
public BankShellForm(WorkItem workItem,
IWorkItemTypeCatalogService workItemTypeCatalog)
: this()
{
this.workItem = workItem;
this.workItemTypeCatalog = workItemTypeCatalog;
/// ADDED
this.sideBarWorkspace = new DockableWindowWorkspace(this.sandDockManager);
this.contentWorkspace = new TabbedDocumentWorkspace(this.sandDockManager);
this.workItem.Workspaces.Add(sideBarWorkspace, "sideBarWorkspace");
this.workItem.Workspaces.Add(contentWorkspace, "contentWorkspace");
///
}
在内部,SandDockManager
为我们完成了大部分工作;自定义 Workspace
只是实现了 IWorkspace
接口的 SandDockManager
的包装器。
IWorkspace
暴露了显示、隐藏、激活和关闭 SmartPart
的方法。自定义 Workspace
只是显示、隐藏、激活和关闭与 SmartPart
关联的 DockableWindow
或 TabbedDocument
。
持久化布局
SandDock
控件支持在应用程序实例之间持久化其布局的概念。这意味着,如果您的精明用户决定花时间重新排列他们喜欢的 Workspace
窗口和标签,SandDockManager
将创建一个 XML 文件,可以将其保存到用户的磁盘并在下次启动应用程序时加载。下面来自 BankShell.cs 的代码是一种快速而粗糙的实现,它将 SandDock
布局保存/加载到名为 SandDockLayout.txt 的文件中,该文件位于应用程序可执行文件所在的目录中。
/// ADDED
private string _ReadLayout()
{
using (FileStream fs = System.IO.File.Open("SandDockLayout.txt",
FileMode.OpenOrCreate, FileAccess.Read))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd();
}
}
}
private void _WriteLayout(string layout)
{
using( FileStream fs = System.IO.File.Open("SandDockLayout.txt",
FileMode.Create, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(layout);
}
}
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
if (!e.Cancel)
{
_WriteLayout(this.sandDockManager.GetLayout());
}
}
///
protected override void OnLoad(EventArgs e)
{
//. . .
/// ADDED
string layout = _ReadLayout();
if (!String.IsNullOrEmpty(layout))
{
try
{
this.sandDockManager.SetLayout(layout);
}
catch { }
}
///
}
这是一个可以利用的绝佳功能,但对于动态加载的 WorkItem
来说可能很难。为了帮助您在应用程序中实现此功能,我创建了 IDockableSmartPart
接口,您可以使用它来标记那些需要持久化布局的 SmartPart
。
IDockableSmartPart
实现者只需为其 SmartPart
定义一个 Guid
,SandDockManager
将使用该 Guid
来识别 SmartPart
在应用程序使用之间的位置和布局。请参阅来自 SideBarView.cs 的示例代码。
public partial class SideBarView : UserControl, IDockableSmartPart
{
private static readonly Guid dspGuid =
new Guid("{B69E82F9-5FA2-4309-8FC0-0F421D12F0EB}");
//. . .
Guid IDockableSmartPart.Guid
{
get { return dspGuid; }
}
}
您可以通过运行应用程序、将 SideBarView
移动到屏幕右侧、关闭应用程序,然后重新打开应用程序来测试 BankTeller
应用程序。SideBarView
现在应该显示在屏幕的右侧。无论您将 SideBarView
放在哪里,它都应该被记住并在每次重新加载时放置在正确的位置。
![[Sidebar on Left]](https://cloudfront.codeproject.com/smart/dockablecabworkspaces/left.gif)
您可以使用内置的 State
和 IStatePersistanceProviders
来让您的 WorkItem
在使用之间记住它们的 Guid
,并将这些 Guid
传递给您的 IDockableSmartPart
,以便它们能够被自定义工作区正确加载和排列。
单元测试
我从复合 UI 应用程序块的源代码中窃取了 TabWorkspace
和 WindowWorkspace
的单元测试,并对它们进行了修改以适应我的需求。如果您在机器上安装了 nUnit,您可以在 WCPierce.Practices.CompositeUI.WinForms.Test
程序集上运行 GUI 界面,并看到所有漂亮的绿色指示灯。我只对现有测试添加了很少的内容,所以我认为 DockableWindowWorkspace
和 TabbedDocumentWorkspace
的大约 90% 的功能都由单元测试覆盖。
(已更新:alexsantos 发布了修复此问题的代码。请使用上面的下载链接获取新代码)。我在 TabbedDocumentWorkspace
中遇到的一个问题是,当一个现有标签页关闭时,如何正确地将焦点设置给兄弟标签页。您中有使用 SandDock 控件经验更丰富的人可能能够解决这个问题。如果有人提出了有效的解决方案,请告诉我,我将将其合并到 Workspace
中。
结论
我想明确一点,代码下载不包含可停靠/可固定窗口框架。所有这些了不起的功能都由 DivElements 的 SandDock
控件提供。代码下载确实包含一个漂亮的小包装器,当与 Microsoft Patterns & Practices 团队的了不起工作结合使用时,它将使您能够以模块化的方式创建高度动态和可定制的应用程序。
我非常感谢评论、评分、反馈等。所以,如果您觉得这篇文章有帮助,或者您认为它很糟糕,请通过下面的评论区告诉我。