作者:adospace.NET 17 Jul 2007 原文链接
WPF Docking Library(库) 可轻松实现窗体停靠的功能类似于 Visual Studio那样。

介绍
最近,我开始了一个项目移植一个Windows窗体应用程序到WPF。 我是WPF的新手,因此起初我正在考虑使用某种类型的Windows窗体互操作性。 特别是,WinForm应用程序具有很酷的停靠功能,我想移植到较新的版本。 当我深入到WPF技术,我发现了一个新的世界,从根本上改变了我最初的想法。 在本文中,我希望共享一个在没有任何Win32-interop的情况下在纯WPF中实现Windows停靠功能的库。
使用代码
有三个基本类:DockManager,DockableContent和DocumentContent。 DockManager是负责管理主窗口布局的类。 Panel 表示可以停靠到边框的窗口区域。 每个Panel包含一个或多个ManagedContent元素,这些元素精确地引用了客户端代码的窗口内容元素。 使用这个库很简单。 DockManager是一个用户控件,可以轻松地嵌入到窗口中。 例如,以下XAML代码在DockPanel中创建一个DockManager:
<Window x:Class="DockingDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="DockingDemo" Height="500" Width="500" xmlns:custom="clr-namespace:DockingLibrary;assembly=DockingLibrary" Loaded="OnLoaded" Background="LightGray" > <DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="File"> <MenuItem Header="New" Click="NewDocument"/> <MenuItem Header="Exit" Click="ExitApplication"/> </MenuItem> <MenuItem Header="Edit"/> <MenuItem Header="Window"> <MenuItem Header="Explorer" Click="ShowExplorerWindow"/> <MenuItem Header="Output" Click="ShowOutputWindow"/> <MenuItem Header="Property" Click="ShowPropertyWindow"/> <MenuItem Header="ToDoList" Click="ShowListWindow"/> </MenuItem> <MenuItem Header="?"/> </Menu> <ToolBar DockPanel.Dock="Top"> <Button>OK</Button> </ToolBar> <custom:DockManager Name ="DockManager"/> </DockPanel> </Window>
注意,要使用DockManager,必须引用一个外部CLR名称空间DockingLibary。 您现在可以使用以下代码将窗口添加到DockManager中:
public partial class MainWindow : System.Windows.Window { private TreeViewWindow explorerWindow = new TreeViewWindow(); private OutputWindow outputWindow = new OutputWindow(); private PropertyWindow propertyWindow = new PropertyWindow(); private ListViewWindow listWindow = new ListViewWindow(); public MainWindow() { InitializeComponent(); } private void OnLoaded(object sender, EventArgs e) { //set window parent for dragging operations dockManager.ParentWindow = this; //Show PropertyWindow docked to the top border propertyWindow.DockManager = dockManager; propertyWindow.Show(Dock.Top); //Show ExplorerWindow docked to the right border as default explorerWindow.DockManager = dockManager; explorerWindow.Show(); //Show ListWindow in documents pane listWindow.DockManager = dockManager; listWindow.ShowAsDocument(); } }
为了可停靠,必须从DockableContent派生一个窗口。 从DockableContent派生表示可以在DockablePane中托管窗口内容。 Windows最初停靠在您设置“显示”成员调用的位置,或者默认情况下停靠在左边框。 最后要看的是如何添加文档窗口。 文档窗口是不能停靠在主窗口边框上的特定窗口。 它仅驻留在DocumentsPane中,作为DockablePane,它是一种特殊的窗格。 以下代码向DockManager添加了具有唯一标题的文档窗口:
private bool ContainsDocument(string docTitle) { foreach (DockingLibrary.DocumentContent doc in DockManager.Documents) if (string.CompareOrdinal(doc.Title, docTitle) == 0) return true; return false; } private void NewDocument(object sender, EventArgs e) { string title = "Document"; int i = 1; while (ContainsDocument(title + i.ToString())) i++; DocumentWindow doc = new DocumentWindow(); doc.Title = title+i.ToString(); DockManager.AddDocumentContent(doc); }
兴趣点
到底如何实现停靠? 我在这里实现了一个简单的算法来管理停靠的窗口之间的关系。 DockingGrid包含用于在逻辑树中组织窗格的代码。 DockManager调用DockingGrid.ArrangeLayout以便组织窗口布局。 当您需要将Pane停靠到主窗口边框时,也可以使用DockingGrid.ChangeDock。 下图显示了窗格的逻辑树。 不要与WPF逻辑树相混淆。

每个节点都是两个窗格或一个窗格和一个子节点组成的组。 为了布置布局,DockingGrid为每个组创建一个WPF网格,该网格具有两列或两行,具体取决于拆分方向。 我希望图像是不言自明的。 无论如何,如果您有兴趣,随时询问更多详细信息。
从VS中可以看到,该库从版本0.1开始支持浮动窗口。 浮动窗口是FloatingWindow类的实例。 这是一个非常特殊的窗口,因为它需要在两个窗口之间“浮动”。 一个是父窗口,在这种情况下为MainWindow,通常是主应用程序窗口。 另一个是FloatingWindow本身拥有的透明窗口,该窗口在拖动操作期间显示。

如上图所示,FloatingWindow通过标题栏上的上下文菜单支持有用的切换选项。 在WinForms中,控制非客户端窗口区域意味着重写WndProc并管理相关消息,例如WM_NCMOUSEMOVE。
在WPF中,我们无权访问发布到窗口的消息。 这是因为一切都由WPF引擎控制,该引擎绘制窗口内容,触发键盘和鼠标事件以及执行许多其他操作。 我知道的截取消息的唯一方法是安装HwndSourceHook并带有我们函数的委托。 以下代码显示了如何管理WM_NCLBUTTONDOWN消息:
protected void OnLoaded(object sender, EventArgs e) { WindowInteropHelper helper = new WindowInteropHelper(this); _hwndSource = HwndSource.FromHwnd(helper.Handle); _hwndSource.AddHook(new HwndSourceHook(this.HookHandler)); } private IntPtr HookHandler( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled ) { handled = false; switch(msg) { case WM_NCLBUTTONDOWN: if (HostedPane.State == PaneState.DockableWindow && wParam.ToInt32() == HTCAPTION) { short x = (short)((lParam.ToInt32() & 0xFFFF)); short y = (short)((lParam.ToInt32() >> 16)); HostedPane.ReferencedPane.DockManager.Drag( this, new Point(x, y), new Point(x - Left, y - Top)); handled = true;} break; } } }
尽管委托签名看起来像一个窗口过程签名,但这不是一回事。 但是,您可以处理所有消息,甚至WM_PAINT。
历史
- 13/05/07 — First preliminary release
- 17/05/07 — Update
- 11/06/07 — Version 0.1: Floating window, many improvements and bug fixes
- 16/07/07 — Version 0.1.1: Some annoying bug fixes
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License(CPOL)的许可。
The Code Project Open License (CPOL)