好,阿杰 — 我把 AvalonDock(WPF 停靠/浮动窗口库) 的基础用法、常见场景与示例代码整理成一份实战指南,覆盖从安装、XAML 布局、动态增删文档/工具窗口、MVVM 集成到布局持久化与主题定制。按步骤来,看完就能在 WPF 项目里快速上手。
AvalonDock 基本用法(速成指南)
1. 简介与安装
- AvalonDock 是 WPF 的停靠(docking)/浮动(floating)窗口框架,常用于 IDE、管理后台那类可停靠面板的桌面应用。
- 推荐使用 Xceed 维护的 NuGet 包:
Xceed.Wpf.AvalonDock
(注意不同 fork/版本 API 细节有差异)。
安装(NuGet):
Install-Package Xceed.Wpf.AvalonDock
# 可加主题包(若有)
# Install-Package Xceed.Wpf.AvalonDock.Themes
常见命名空间
using Xceed.Wpf.AvalonDock;
using Xceed.Wpf.AvalonDock.Layout;
using Xceed.Wpf.AvalonDock.Layout.Serialization;
using Xceed.Wpf.AvalonDock.Themes;
2. 核心概念
- DockingManager:根控件,管理整个停靠布局与行为。
- LayoutRoot:布局根(包含面板、文档区、锚定区等)。
- LayoutDocument / LayoutDocumentPane:主要用于“文档”类型的窗口(像编辑器标签页)。
- LayoutAnchorable / LayoutAnchorablePane:工具窗口(可停靠在四周、可折叠、浮动)。
- XmlLayoutSerializer:序列化/反序列化布局(保存与还原用户自定义布局)。
- Theme:主题(外观样式),例如 VS2013、Metro 等(依版本而异)。
3. XAML 基本布局示例
一个最小可用的布局,包含文档区域与左侧工具窗口:
<Window x:Class="Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ad="http://avalondock.codeplex.com"
Title="AvalonDock Demo" Height="600" Width="900">
<Grid>
<ad:DockingManager x:Name="DockManager">
<ad:DockingManager.Theme>
<!-- 根据包版本使用可用的主题类 -->
<ad:VS2013LightTheme />
</ad:DockingManager.Theme>
<ad:LayoutRoot>
<ad:LayoutPanel Orientation="Horizontal">
<!-- 左侧工具区 -->
<ad:LayoutAnchorablePaneGroup DockWidth="200">
<ad:LayoutAnchorablePane>
<ad:LayoutAnchorable Title="工具" CanClose="False">
<TextBlock Text="工具窗口内容" Padding="10"/>
</ad:LayoutAnchorable>
</ad:LayoutAnchorablePane>
</ad:LayoutAnchorablePaneGroup>
<!-- 主文档区 -->
<ad:LayoutDocumentPane x:Name="DocumentPane">
<ad:LayoutDocument Title="欢迎">
<TextBlock Text="这里是文档区域" Padding="10"/>
</ad:LayoutDocument>
</ad:LayoutDocumentPane>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
</Grid>
</Window>
注意:不同版本的 AvalonDock XML 命名空间与 Theme 类名可能略有差异,上例中
VS2013LightTheme
仅作示例,按你的包文档选择实际可用的主题类。
4. 在代码里动态创建/添加文档与工具窗口
推荐做法:在 XAML 中预定义 LayoutDocumentPane
(如上例的 x:Name="DocumentPane"
),在代码里直接向它添加子项。
C# 示例(窗口 code-behind):
using Xceed.Wpf.AvalonDock.Layout;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AddDocument(string title, UIElement content)
{
var doc = new LayoutDocument { Title = title, Content = content };
DocumentPane.Children.Add(doc);
doc.IsActive = true;
}
private void AddToolWindow(string title, UIElement content)
{
var anchorable = new LayoutAnchorable { Title = title, Content = content, CanClose = true };
// 把工具窗口加入到第一个 AnchorablePane(这里简单示例)
var anchorPane = DockManager.Layout.RootPanel.Children
.OfType<LayoutAnchorablePaneGroup>()
.SelectMany(g => g.Children)
.OfType<LayoutAnchorablePane>()
.FirstOrDefault();
if (anchorPane != null)
anchorPane.Children.Add(anchorable);
else
{
// 若没有则创建一个新的锚定面板并插入布局(更复杂的场景需要更精细的布局操作)
}
anchorable.IsActive = true;
}
}
更稳妥的做法是预先在 XAML 中定义命名的 LayoutAnchorablePane
,然后直接在 code-behind 使用该命名控制引用,避免复杂的 LayoutRoot 操作。
5. MVVM 风格(视图模型绑定)
通常不建议直接把视图对象作为 LayoutDocument.Content,应该把 ViewModel 绑定到 LayoutDocument,并用 DataTemplate 映射 ViewModel 到 View。
示例:
ViewModels:
public class DocumentViewModel
{
public string Title { get; set; }
public string Text { get; set; }
}
XAML DataTemplate & Layout(简化):
<Window.Resources>
<DataTemplate DataType="{x:Type vm:DocumentViewModel}">
<TextBox Text="{Binding Text}" AcceptsReturn="True" />
</DataTemplate>
</Window.Resources>
<!-- 在代码里创建 LayoutDocument 并把 ViewModel 作为 Content -->
在 code-behind / ViewModel layer:
var vm = new DocumentViewModel { Title = "Doc1", Text = "..." };
var doc = new LayoutDocument { Title = vm.Title, Content = vm };
DocumentPane.Children.Add(doc);
AvalonDock 会使用 DataTemplate 把 DocumentViewModel
渲染成 TextBox
。这允许遵循 MVVM,不直接把 UI 元素塞入布局。
6. 布局持久化:保存与还原用户布局
使用 XmlLayoutSerializer
保存当前布局到 XML 文件,启动时读取恢复。
保存示例:
using (var stream = new StreamWriter("layout.config"))
{
var serializer = new XmlLayoutSerializer(DockManager);
serializer.Serialize(stream);
}
加载示例(启动时):
if (File.Exists("layout.config"))
{
var serializer = new XmlLayoutSerializer(DockManager);
using (var stream = new StreamReader("layout.config"))
{
serializer.Deserialize(stream);
}
}
注意事项:
- 反序列化时,如果 Layout 中引用了 Content(视图或 ViewModel)需要提供
Deserialize
的回调把占位符内容替换成实际对象。XmlLayoutSerializer 支持事件/回调(例如LayoutSerializationCallback
),用于在反序列化时注入实际内容:
var serializer = new XmlLayoutSerializer(DockManager);
serializer.LayoutSerializationCallback += (s, e) =>
{
if (e.Model is LayoutDocument docModel)
{
// 根据 docModel.ContentId 或 Title 创建并赋值 Content(ViewModel 或 View)
e.Content = CreateContentFor(docModel.ContentId);
}
};
serializer.Deserialize("layout.config");
- 如果你把 ViewModel(可序列化的 ID)作为 Content,反序列化时利用 ID 恢复它会比较稳妥。
7. 主题与样式
AvalonDock 支持多种主题(取决于包),常见有 VS2013、Aero、Metro 等。设置方式示例:
DockManager.Theme = new VS2013LightTheme(); // 或 VS2013BlueTheme / MetroTheme 等(依版本)
或在 XAML 中声明 Theme 节点(参考包文档示例)。
8. 常见事件与 API(快速参考)
LayoutDocument.Closing
:文档关闭事件(可用于阻止关闭、提示保存)。DockingManager.ActiveContent
:当前激活的内容。LayoutDocument.IsActive
/LayoutAnchorable.IsActive
:设为 true 可激活对应面板。LayoutDocumentPane.Children
/LayoutAnchorablePane.Children
:添加/移除子项。
9. 常见问题与最佳实践
- 不要在反序列化时直接依赖 UI 元素。用 ContentId 或 ViewModel ID,在
LayoutSerializationCallback
中动态创建内容。 - MVVM:把 Document/Anchorable 的
Content
指向 ViewModel,并使用 DataTemplate 映射为 View。 - 命名与 ContentId:在序列化前为每个文档/工具窗口设置唯一
ContentId
,便于恢复。 - 并发/线程:所有 AvalonDock 操作必须在 UI 线程(Dispatcher)中执行。
- 布局自定义:复杂动态布局建议先在 XAML 里预置容器(DocumentPane、AnchorablePane),在运行时只做添加/移除,避免频繁修改 LayoutRoot 结构。
- 主题兼容:不同 AvalonDock 版本主题类名不同,按你安装的包来选择主题类。
10. 典型用例:基于 MVVM 的最小运行流程
- 在 XAML 定义
DockingManager
、LayoutDocumentPane
(命名)和DataTemplate
(把 ViewModel 映射成 View)。 - 在主 VM 中维护
ObservableCollection<DocumentViewModel>
。 - 当用户打开一个新文件时,创建
DocumentViewModel
并在主 VM(或 code-behind)中创建LayoutDocument
并把 ViewModel 赋给Content
并加入DocumentPane.Children
。 - 保存布局时序列化;重启加载时利用
LayoutSerializationCallback
恢复 ViewModel -> Content。
11. 小 Demo(关键片段汇总)
XAML(含 DataTemplate 与 DocumentPane):
<ad:DockingManager x:Name="DockManager">
<ad:LayoutRoot>
<ad:LayoutPanel Orientation="Horizontal">
<ad:LayoutAnchorablePaneGroup DockWidth="200">
<ad:LayoutAnchorablePane x:Name="ToolPane" />
</ad:LayoutAnchorablePaneGroup>
<ad:LayoutDocumentPane x:Name="DocumentPane" />
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:DocumentViewModel}">
<local:DocumentView /> <!-- DocumentView 是展示该 VM 的 UserControl -->
</DataTemplate>
</Window.Resources>
创建文档(Code-behind / ViewModel 层触发):
var vm = new DocumentViewModel { Title = "New" };
var doc = new LayoutDocument { Title = vm.Title, ContentId = Guid.NewGuid().ToString(), Content = vm };
DocumentPane.Children.Add(doc);
doc.IsActive = true;
序列化回调(反序列化时恢复 Content):
var serializer = new XmlLayoutSerializer(DockManager);
serializer.LayoutSerializationCallback += (s, e) =>
{
if (e.Model is LayoutDocument docModel)
{
// 以 ContentId 恢复 ViewModel
e.Content = MyViewModelFactory.CreateForContentId(docModel.ContentId);
}
};
serializer.Deserialize("layout.config");
12. 想要我帮你做什么?(可选交付)
我可以帮你进一步交付下面任意一项:
- ✅ 给你生成一个完整的 可运行 WPF 示例工程(MVVM、AvalonDock、布局持久化、主题切换)并打包成 zip。
- ✅ 把关键步骤写成 PDF Cheatsheet(包含常用 API、保存/加载布局代码、MVVM 示例)。
- ✅ 或只给你一个最简 Repository 的 README + 代码片段,方便直接 clone 并运行。
告诉我你要哪一个(或直接回复“示例工程”),我就直接生成并把下载链接给你。
好 👌 阿杰,那我给你准备 AvalonDock 学习大礼包,包括:
- PDF 速查表 📑
- 安装与引用方式
- 核心概念总结(DockingManager、LayoutRoot、Document/Anchorable 等)
- 常用 XAML 模板
- 动态添加/移除文档窗口与工具窗口的代码片段
- MVVM + DataTemplate 示例
- 布局保存与加载(XmlLayoutSerializer + 回调)
- 常见坑点与最佳实践
- Markdown 版笔记 📘
- 适合放在 Obsidian / Typora / GitHub Repo 里
- 代码高亮 + 简化清单式说明
- 完整可运行示例工程 (WPF + AvalonDock) 🛠️
- MVVM 架构
- 主窗口带 DockingManager,包含 DocumentPane + ToolPane
- 动态添加文档、关闭文档
- 工具窗口可停靠/浮动
- 主题切换(VS2013 Light/Blue)
- 布局保存与恢复(关闭应用保存,启动时加载)
发表回复