[译]WPF停靠类库

作者: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)

发表评论

电子邮件地址不会被公开。 必填项已用*标注

1 × 1 =