AvalonDock [2.0] 教程第一部分 - 添加工具窗口






4.97/5 (35投票s)
如何在 AvalonDock [2.0] 中创建新的工具窗口
目录
引言
"AvalonDock 是一个 WPF 控件库,可用于创建类似于 Visual Studio 中存在的停靠布局系统。它支持弹出窗格、浮动窗口、同一窗口中的多个停靠管理器、样式和主题,并且可以托管 WinForms 控件。" (引用自原始 codeplex 项目)
AvalonDock 是一个非常完整且稳定的 .NET/WPF 开源项目,提供了此功能,并且已经开发了五年。当前解决方案是向前迈出的一步,因为它从头开始构建,支持 MVVM,可主题化,并且可以本地化。
AvalonDock 由 Adolfo Marinucci 编写。我并未以任何方式参与 AvalonDock 的开发,但认为现在是时候记录 一些基本内容,并在此期间创建了自己的存储库(如果您想贡献)https://github.com/Dirkster99/AvalonDock。
教程的前两部分涵盖了初始准备步骤以及添加最近使用的文件 (MRU) 工具窗口和开始页。之后可能还会有关于其他 AvalonDock 主题(主题、本地化、AvalonDock 2.0 中的 AvalonEdit)的文章。那些等不及后续文章的人可以查看 Edi(https://github.com/Dirkster99/Edi),在我在此处记录之前了解一些内容。本文实际上是使用 Edi 编写的。
解决方案准备
- 从 GitHub 下载 AvalonDock 源代码(我使用了 2012 年末的 CodePlex 构建 98289,该版本已不再可用,但此处链接的版本应该足够接近)
https://github.com/Dirkster99/AvalonDock/tree/4bcb07f45e89e416257e668a209b238a26e5f414
- 找到 2.0 版本子文件夹并删除所有其他子文件夹。
- 查看 2.0 版本子文件夹中的解决方案,并删除除 AvalonDock.MVVMTestApp 和 AvalonDock 之外的所有项目。
我最终删除了子项目
- AvalonDock.TestApp.csproj
- AvalonDock.WinFormsTestApp.csproj
- AvalonDock.Theme.VS2010 (主题资源库)
- AvalonDock.Themes.Aero (主题资源库)
- ...以及其他主题项目。
并剩下
- AvalonDock (停靠布局系统库)
- AvalonDock.MVVMTestApp (将在本文中扩展的测试应用程序)
- 添加一个新的 WPF 应用程序项目,应用程序名称为您希望基于 AvalonDock 开发的名称(我的名称是 Edi),并将文件从 AvalonDock.MVVMTestApp 复制到新项目中。您也可以按照 Version_01_Edi.zip 中所示,将项目整理到文件夹和命名空间中。您的解决方案应该如下所示
让我们开始吧
打开解决方案,在 Visual Studio 中将 **Edi** 项目设置为启动项目。按 F5 键检查应用程序是否正常运行。在我这里看到了以下内容
最初的测试应用程序框架(在我这里)由一个空的文档面板和一个底部的工具窗口组成。您可以尝试菜单和工具窗口,以熟悉已有的功能。我们将通过开始页和第二个工具窗口来扩展此功能(两者都主要显示最近使用的文件列表)。
添加最近使用的文件列表控件
我在之前的系列文章中记录了一种通过组合和主题化开发(无外观)控件的方法(https://codeproject.org.cn/Articles/332541/WPFControlCompositionPart2of2)。该库的扩展版本包含一个列表视图,可以容纳超链接,这些超链接经过自定义,使得
- 超链接中的路径显示为 URL 中间的省略号(基于:https://codeproject.org.cn/script/Articles/ArticleVersion.aspx?aid=467054&av=668101&msg=4381519#xx4381519xx)。
- 每个超链接控件都提供一个自定义的上下文菜单。
- 单击超链接会打开链接的文件。
- 此外,用户还可以固定一个条目。固定条目意味着显示一个针形图钉,并且在稍后打开其他文件时,该条目不会被删除。
实现上述要求的部件是
- Hyperlink 子文件夹:无外观超链接控件位于此处。
- MRU 子文件夹:包含 MRU 模型、ViewModel 和 View 组件(基于:http://social.msdn.microsoft.com/Forums/en/wpf/thread/3308367d-0174-4389-9586-2875712bed73)。PathTrimmingTextBlock 子文件夹:包含一个路径截断控件,用于在中间显示带省略号的路径(基于:https://codeproject.org.cn/script/Articles/ArticleVersion.aspx?aid=467054&av=668101&msg=4381519#xx4381519xx)。
我现在不深入讨论这些内容,因为我真的想专注于 AvalonDock [2.0]。如果您真的想了解更多关于 MRU 实现的信息,请阅读引用的文章。如果还不行,请随时在文章下方提问。
回到我们的 AvalonDock 集成项目。下载并解压 Version_02_Edi_RecentFilesTW.zip 文件。这应该会给您一个包含最近使用的文件工具窗口的可用解决方案。只需打开几次文件,即可在工具窗口中生成一些超链接条目,并注意文件子菜单中的额外 MRU 条目。
此解决方案如何工作以及需要什么才能获得它?将在本文的其余部分讨论。
最近使用的文件 (MRU) 工具窗口 (ViewModel)
我喜欢尽可能使用 RoutedUICommand
,因此我添加了 AppCommand
类,其中包含一个静态构造函数和以下命令
LoadFile
PinUnpin
AddMruEntry
RemoveMruEntry
当用户打开文件时,将执行 LoadFile 命令。当用户单击最近使用的文件列表中的条目时,有必要打开文件。PinUnpin 命令会反转 MRU 中图钉的状态。其他两个命令 AddMruEntry
和 RemoveMruEntry
用于添加或删除条目。您可以通过从 AppCommand
类跟踪到 Workspace
类(Workspace
类类似于其他人常称的 ApplicationViewModel 或 MainViewModel。它是该项目中所有 ViewModels 的根)的引用来确定它们的功能:
public void InitCommandBinding(Window win)
{
win.CommandBindings.Add(new CommandBinding(AppCommand.LoadFile,
(s, e) =>
{
if (e == null)
return;
string filename = e.Parameter as string;
if (filename == null)
return;
this.Open(filename);
}));
win.CommandBindings.Add(new CommandBinding(AppCommand.PinUnpin,
(s, e) =>
{
this.PinCommand_Executed(e.Parameter, e);
}));
win.CommandBindings.Add(new CommandBinding(AppCommand.RemoveMruEntry,
(s, e) =>
{
this.RemoveMRUEntry_Executed(e.Parameter, e);
}));
win.CommandBindings.Add(new CommandBinding(AppCommand.AddMruEntry,
(s, e) =>
{
this.AddMRUEntry_Executed(e.Parameter, e);
}));
}
每个 MRU 命令执行相应的 Executed
方法,LoadFile 命令执行 Workspace 类中的 Open()
方法。此外,MRU 实现在 Workspace 属性 RecentFiles
中。正是这个属性的数据在 GUI 中可见。但它是如何到达那里的,以及扩展 AvalonDock [2.0] 来显示它需要什么?接下来我们讨论。
最近使用的文件工具窗口 (View)
合并 V01_Edi.zip 和 V02_Edi_RecentFilesTW.zip 中的版本表明,我在 View
命名空间中添加了一个 RecentFilesView
。这是 AvalonDock [2.0] 将在其工具窗口停靠控件中显示的视图。它绑定到 Workspace
类中的 RecentFilesViewModel
属性。
View
AvalonDock 使用 DataTemplateSelector
(View.Pane.PanesTemplateSelector
) 来确定显示需要的数据项时的适当视图。我扩展了这个类,添加了 RecentFilesViewTemplate
属性
public DataTemplate RecentFilesViewTemplate
{
get;
set;
}
...以及 SelectTemplate
方法中相应的语句
if (item is RecentFilesViewModel)
return RecentFilesViewTemplate;
这段代码与 MainWindow.xaml 文件中的此视图扩展协同工作
<pane:panestemplateselector.recentfilesviewtemplate>
<datatemplate>
<view:recentfilesview>
</view:recentfilesview>
</pane:panestemplateselector.recentfilesviewtemplate>
样式
样式 PanesStyleSelector
控制 ViewModel 中的字段如何映射到 AvalonDock 的主要功能(例如,显示项目名称,或用于关闭的命令等)。同样,我们可以用一个附加属性来扩展 View.Pane.PanesStyleSelector
类
public Style RecentFilesStyle
{
get;
set;
}
...以及 SelectStyle
方法中的相应逻辑
if (item is RecentFilesViewModel)
return RecentFilesStyle;
...这与 MainWindow.xaml 中的此 XAML 协同工作
<pane:panesstyleselector.recentfilesstyle>
<style targettype="{x:Type avalonDock:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.Title}"/>
<Setter Property="IconSource" Value="{Binding Model.IconSource}"/>
<Setter Property="Visibility" Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter={x:Static Visibility.Hidden}}"/>
<Setter Property="ToolTip" Value="{Binding Model.FilePath}"/>
<Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"/>
<Setter Property="ContentId" Value="{Binding Model.ContentId}"/>
</style>
</pane:panesstyleselector.recentfilesstyle>
Visibility
属性上(带有转换器)的非常长的一行对于使菜单项 *工具*>**最近使用的文件* 工作至关重要。首选方法是命令绑定,但为了简单起见,我们这里只设置了一个布尔属性。布尔属性 IsVisible
位于 RecentFilesViewModel
的派生类中。也就是说,它存在于 RecentFilesViewModel
中,因为它继承自 Base.ToolViewModel
类。
BoolToVisibilityConverter 在布尔值和 Visibility
属性之间进行转换。这是必需的,因为布尔值有 2 个状态(true,false),而 WPF 中的 Visibility
有 3 个状态(Visible,Hidden,Collapsed)。例如,要了解更多关于转换器及其在 WPF 中应用的信息,请阅读 [3] 中引用的文章。
最近使用的文件菜单项
最近使用的文件菜单项,位于 **文件**>***最近使用的文件***,由此 XAML 代码定义(来自 MainWindow.xaml)
<MenuItem ItemsSource="{Binding RecentFiles.MruList.ListOfMRUEntries}" Grid.Row="0" Header="Recent Files"
Visibility="{Binding Path=RecentFiles.MruList.ListOfMRUEntries, Mode=OneWay, Converter={conv:ZeroToVisibilityConverter}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding DisplayPathFileName, Mode=OneWay}" />
<Setter Property="Command" Value="cmd:AppCommand.LoadFile" />
<Setter Property="CommandParameter" Value="{Binding PathFileName, Mode=OneWay}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
在这里,您可以通过 ItemsSource
绑定看到每个条目直接来自 RecentFiles.MruList.ListOfMRUEntries
。其中 RecentFiles
是 Workspace
类的属性,MruList
是 RecentFilesViewModel
ViewModel 的属性。因此,菜单项集合的来源实际上是由 MRUListVM
类管理的。
当用户单击最近使用的文件菜单项时执行的命令是 LoadFile 命令,该命令在 AppCommand
类中实现为 RoutedUICommand。但 AppCommand 类仅提供命令功能,实际逻辑在 Workspace
类中的 InitCommandBinding
方法中实现(请参阅本文前面的 LoadFile 代码列表)。
自定义 AvalonDock 2.0
这是使 AvalonDock 如此出色的控件之一。用户实际上可以从主窗口拖出项目(例如,将文档拖到第二个显示器上)并继续处理。我经常看到人们询问如何配置应用程序以决定用户是否可以实际拖出项目。
在 AvalonDock LayoutItem
的样式中添加以下设置
<Setter Property="CanFloat" Value="False" />
以禁用弹出功能。您甚至可以将其绑定到 ViewModel 中的属性,以便在运行时配置此设置。
更新到 AvalonDock 3.x
本节附带的演示源代码显示了如何将下载中的 **版本 02** 更新到 AvalonDock **版本 3.4**。版本 02 和版本 03 之间的主要区别是
- AvalonDock 控件库可以从 Nuget 下载
- AvalonDock 命名空间已从
AvalonDock
更改为Xceed.Wpf.AvalonDock
因此,更新到当前版本很容易,并且也应该适用于本系列文章的其他部分。
摘要
......就这样。这就是在 AvalonDock 中添加新工具窗口所需的大部分内容。让我们为 Adolfo Marinucci 鼓掌,他以如此简化的方式完成了如此复杂的工作。
我们终于实现了视图和 ViewModel 之间的关注点分离,并且有一个简单的模式可循。不要忘记 AvalonDock 开箱即用的停靠和弹出功能。现在,谁认为 MVVM 太复杂,难以实现?再想想。
在下一篇文章中,我们将讨论创建新文档 - 不要忘记就本文给我您的反馈,并在您想学习如何为应用程序添加开始页时继续进行下一阶段 [1]。
参考文献
- [1]
AvalonDock [2.0] 教程第二部分 - 添加开始页
https://codeproject.org.cn/Articles/483533/AvalonDock-2-0-Tutorial-Part-2-Adding-a-Start-Page
AvalonDock [2.0] 教程第三部分 - AvalonDock 中的 AvalonEdit
https://codeproject.org.cn/Articles/570313/AvalonDock-2-0-Tutorial-Part-3-AvalonEdit-in-Avalo
AvalonDock [2.0] 教程第四部分 - 集成 AvalonEdit
https://codeproject.org.cn/Articles/570324/AvalonDock-2-0-Tutorial-Part-4-Integrating-AvalonE
AvalonDock [2.0] 教程第五部分 - 加载/保存布局,带解引用 DockingManager
https://codeproject.org.cn/Articles/719143/AvalonDock-2-0-Tutorial-Part-5-Load-Save-Layout-wi
- [2] AvalonDock
https://github.com/Dirkster99/AvalonDock
- [3]
在 WPF 中使用 ValueConverter 和 MultiValueConverter
https://codeproject.org.cn/Articles/298950/Using-ValueConverter-and-MultiValueConverter-in-WP
历史
- 24.20.2012 初始创建。
- 01.03.2013 更新了 AvalonDock 版本并简化了文章和源代码。
- 29.10.2018 添加了 AvalonDock 3.x 部分