65.9K
CodeProject 正在变化。 阅读更多。
Home

构建 WPF 的停靠窗口管理解决方案

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.28/5 (24投票s)

2010 年 12 月 27 日

CPOL

8分钟阅读

viewsIcon

200305

downloadIcon

15794

使用 WPF 作为 Synergy 工具包一部分的停靠窗口解决方案。

引言

窗口停靠是多窗口应用程序中常见的行为。作为一名用户界面开发者,这种行为一直让我着迷,因此我考虑在自己的 WPF 工具包中开发类似的功能。我知道市面上已经有很多类似的解决方案实现,有些甚至是开源的,但我的目标是把这个个人项目作为一个挑战和学习的机会。

我还应该提到,我实现的停靠解决方案是我在我公司 MixModes Inc 正在持续构建的一个更大的 WPF 工具包的一部分,但我打算将其保持为开源且免费的项目。要预览此库的完整功能集,您可以访问我的博客 这里

许多现有的窗口停靠解决方案都有浮动窗口作为 MDI 父窗口下管理的独立窗口。但我将浮动窗口严格包含在父窗口内,因为我打算很快将此代码移植到 Silverlight。

我还在 CodePlex 上托管了这个项目:http://mixmodessynergy.codeplex.com/

Synergy 工具包的结构

MixModes Synergy 工具包包含以下顶级项目:

  • MixModes.Synergy.Resources – 包含图像和语言资源
  • MixModes.Synergy.Themes – 定义用户控件、自定义窗口、颜色、画笔和文本主题的默认主题
  • MixModes.Synergy.Utilities – 公共实用工具
  • MixModes.Synergy.VisualFramework – 包含行为、装饰器、命令、用户控件、停靠框架和其他 WPF 特定功能
  • Synergy – 突出显示工具包功能的示例项目

可停靠窗口的结构

要理解窗口停靠解决方案,有必要理解驱动这一切的最基本控件——可停靠窗口。可停靠窗口是一个特殊的窗口,除了内容和标题之外,还可以处于以下几种状态:

  1. 固定状态(Pinned state)– 可停靠窗口可以固定在父窗口的侧边,以保持一致的可见性。通常,常用内容会被固定以便于访问。

    1.png

  2. 自动隐藏状态(Auto hidden state)– 不常用窗口可以自动隐藏,这样当鼠标不悬停在它们上面时,它们会折叠成一个紧凑的形式(我称之为标题栏)。当鼠标悬停在标题栏上时,完整窗口会从停靠的一侧滑出。

    2.png

  3. 文档状态(Document state)– 当用作文档时,可停靠窗口可以作为选项卡项合并到选项卡控件中。

    3.png

  4. 浮动(Floating)– 常规浮动窗口

    4.png

停靠窗口支持由 DockPane 控件提供,该控件是 HeaderedContentControl 的派生类。除了它从 HeaderedContentControl 继承的 Header Content 属性外,它还包含以下属性:

  • Icon – 停靠窗口的图标
  • CondencedDockPanelTemplate – 紧凑型 DockPane 的模板
  • CondencedDockPanel DockPane 的紧凑形式
  • DockPaneState – 停靠窗格的状态

DockPane 还包含以下事件:

  • Close – 关闭事件
  • TogglePin – 切换固定/自动隐藏按钮
  • HeaderDrag – 通知用户已开始拖动 DockPane 的标题栏

DockPane 的默认主题定义在 MixModes.Synergy.Themes 项目中的 DockPane.xaml 资源字典中。

文档容器

DockPane(s) 包含在文档容器中。文档容器通过 DocumentContainer 类建模,该类是 ContentControl 的派生类。DocumentContainer 可以处于以下几种(互斥的)状态之一:

  • Empty DocumentContainer 不包含任何 DockPane
  • ContainsDocuments DocumentContainer 包含一个或多个 DockPane(s) 作为文档
  • SplitHorizontally DocumentContainer 被水平分割
  • SplitVertically - DocumentContainer 被垂直分割

5.png

DocumentContainer 的模板很复杂,因为它需要表示分割视图或选项卡视图。我在模板中使用了持久的 TabControl,如果 Content 属性始终为 null,则隐藏该 TabControlContent 当然是专门用于通过带有两个 DocumentContainer 子项的 Grid 来包含分割视图。

DocumentContainer 包含以下属性:

  • State DocumentContainer 的状态
  • Documents – 包含在 TabControl 中显示的文档
  • DocumentsTab – 包含文档的 TabControl
  • DockIllustrationPanel – 这是一个包含停靠说明点(docking illustration points)的面板,指示用户内容应该停靠在哪里。如果用户将 DockPane 拖动到其中一个点,它就会被停靠在相应的位置。下图显示了 DockIllustrationPanel 的内容。

6.png

DocumentContainer 包含以下方法:

  • AddDockPane(DockPane, ContentDockPoint) – 将 DockPane 添加为停靠窗口
  • AddDocumentContainers(IEnumerable<DocumentContainer>, bool) – 分割并添加子 DocumentContainers
  • AddDocument(DockPane) – 将 DockPane 添加为选项卡式文档
  • RemoveDocument(DockPane) – 将 DockPane 从选项卡式文档中移除

DocumentContainer 的模板包含以下分层视觉元素(底部视觉元素 Z 顺序递增):

  • TabControl (PART_DOCUMENTS) – 绑定到 DocumentContainerDocuments 属性
  • ContentPresenter – 分割子项将添加到此处
  • Grid (PART_DOCK_POINTS) – 用于容纳停靠说明点的面板
  • Grid (PART_DOCK_ILLUSTRATION) DockIllustrationPanel,用于通过提示说明未来的停靠

窗口管理器

窗口管理器是连接 DockPanel(s) 和 DocumentContainer(s) 以在应用程序中提供窗口管理功能的组件。此外,窗口管理器在所有四个窗口侧边包含自动隐藏和固定的停靠点,因此 DockPane(s) 可以固定或自动隐藏在 DocumentContainer(s) 之外。WindowsManager 还包含根 DocumentContainer,它可以托管选项卡控件中的文档,或托管嵌套分割的 DocumentContainer 实例。

WindowsManager 具有以下属性:

  • DockPaneIllustrationStyle – 在 WindowsManagerDocumentsContainer 中停靠窗口的说明样式
  • DockIllustrationContentStyle – 在拖动 DockPaneTabControl 中合并文档时的说明样式
  • ActiveWindowsManager – 指示正在进行拖动操作的 WindowsManager 的静态属性
  • DraggedPane – 正在被拖动的 DockPane
  • <Orientation>WindowHeaders – 包含自动隐藏的紧凑型 DockPane(s) 的 StackPanel
  • <Orientation>PinnedWindows – 包含固定的 DockPane(s) 的 DockPanel
  • DocumentContainer – 根文档容器
  • DockingIllustrationPanel – 用于将来固定 DockPanel(s) 的停靠说明面板
  • PopupArea – 当鼠标悬停在紧凑型标题栏上时,自动隐藏的 DockPane(s) 会滑出的 DockPanel
  • FloatingPanel – 包含浮动 DockPane(s) 的 Canvas
  • DockingPanel – 包含固定窗口停靠点的 DockPanel,如下面的图像所示

7.png

WindowsManager 具有以下方法:

  • AddPinnedWindow(DockPane, Dock) – 添加一个固定的 DockPane
  • AddAutoHideWindow(DockPane, Dock) – 添加一个自动隐藏的 DockPane
  • AddFloatingWindow(DockPane) – 添加一个浮动的 DockPane
  • RemoveDockPane(DockPane) – 从 WindowsManager 的(固定、自动隐藏或浮动部分)移除 DockPane
  • Clear – 清除 WindowManager 中的所有 DockPane(s)
  • StartDockPaneStateChangeDetection – 开始监视 DraggedPane 的状态
  • StopDockPaneStateChangeDetection – 停止监视 DraggedPane 的状态

所有组件如何协同工作

下图说明了停靠解决方案中各个组件之间的关系。

8.png

结构上,WindowsManager 是包含固定和自动隐藏 DockPane(s) 的包容组件。它还包含根 DocumentContainer。另一方面,DocumentContainer 可以通过将 DockPane 包装在 DocumentContent 实例中来包含选项卡控件中的文档,或者它可以包含分割窗口,其中一个网格包含子 DocumentContainer(s),每个子 DocumentContainer 都可以递归地包含文档或更多的子 DocumentContainer(s)。

WindowsManager 会持续监视 DockPane 的状态变化。当检测到 DockPane 拖动时,它会被放置在 WindowsManagerFloatingPanel 画布上,作为一个可以四处拖动的浮动窗口。在拖动 DockPane 的过程中,DockPane 上的命中测试会被关闭,以便鼠标事件能够向下传递到下面的控件,例如 WindowsManagerDocumentContainer

为了协调拖放和停靠功能,我采用了基于行为的方法。其思想是在视觉实体(如 DockPaneDocumentContainerWindowsManager)上公开功能端点(如方法和属性),并使用行为来协调和调用这些功能端点。这种方法也产生了易于管理和封装的组件,更易于追踪和测试。

DockPointBehaviorWindowsManager 上监视拖动的 DockPane 并弹出用于固定的停靠点。而 ContentPointBehaviorDocumentContainer 中注入类似的功能,用于分割和选项卡合并目的。

WindowsManagerDocumentContainer 都拥有包含停靠行为的停靠说明网格。DockPointBehaviorWindowsManager 上说明固定停靠,而 ContentDockBehaviorDocumentContainer 上说明分割和选项卡合并。

Using the Code

使用 WindowsManager 非常简单:

  • 在 XAML 中导入命名空间
    xmlns:visualFx="http://mixmodes.com/visualFx"  
  • WindowsManager 放入 XAML 中
    <visualFx:WindowsManager x:Name="WindowsManager"/>  
  • 开始创建 DockPane 并将它们插入 WindowsManager / DocumentContainer
    DockPane pane = new DockPane();
    pane.Header = …  
    pane.Content = …. 
    WindowsManager.AddPinnedWindow(pane, Dock.Top); 
    // OR 
    WindowsManager.AddAutoHideWindow(pane, Dock.Left); 
    // OR 
    // Assuming DocumentContainer is either in Empty or ContainsDocuments state 
    WindowsManager.DocumentContainer.AddDocument(pane); 

序列化窗口状态

Synergy 开箱即用,通过 XmlWindowsManagerSerializerXmlWindowsManagerDeserializer 类提供窗口状态的 XML 序列化。通过分别特化基类 WindowsManagerSerializerWindowsManagerDeserializer 来支持自定义序列化。

使用 XmlWindowsManagerSerializer 进行 WindowsManager 的 XML 序列化需要在构造时提供两类信息:

  • DockPane 写入器 – 一个 Action<XmlElement, DockPane> 实例,可以将 DockPane 的额外元数据写入 XmlElement

  • Document 写入器 – 一个 Func<DocumentContent, string> 实例,它接受一个 DocumentContent 并返回内容的字符串表示。注意DocumentContent.DockPane 属性返回关联的 DockPane,但 DockPaneHeaderContent 属性设置为 null。要访问 HeaderContent 属性,请直接使用 DocumentContent 实例的 HeaderContent 属性。

一旦创建了 XmlWindowsManagerSerializer 实例,调用 Serialize(Stream, WindowsManager) 方法即可将 WindowsManager 序列化到 stream

与序列化过程类似,反序列化过程需要在 XmlWindowsManagerDeserializer 的构造函数中提供一个 Action<DockPane, string> 实例,以便从先前保存的字符串表示中反序列化 DockPane。反序列化不需要额外的 Action 来实现 DocumentContent,因为 DocumentContent 本质上是 DockPane 的序列化包装器。

一旦创建了 XmlWindowsManagerDeserializer 实例,调用 Deserialize(Stream, WindowsManager) 即可将 WindowsManager 反序列化到先前保存的状态。

所有停靠功能都可以通过使用带有源代码的示例应用程序(Synergy 项目)来练习。一如既往,欢迎任何评论、建议或 bug 报告。祝编码愉快!

© . All rights reserved.