WPF Docking Library

转载 2011年01月22日 00:12:00

http://www.codeproject.com/KB/WPF/WPFdockinglib.aspx

http://www.codeproject.com/KB/WPF/WPFDockingToolkit.aspx

Introduction

 

 

Window docking is a familiar functionality in multi-windows applications. As a user interface developer this behavior has always charmed me and so I thought of developing the same functionality in my own WPF toolkit. I know there are many implementations of similar solutions out there, some even open source, but my aim was to take this personal project as a challenge and learning opportunity.

I should also mention that the docking solution I have implemented is part of a bigger WPF toolkit that I am building on an ongoing basis under my company MixModes Inc, however I intend to keep it open source and royalty free project. To preview the full feature set of this library you can visit my blog here.

Many existing implementations of window docking solutions have floating windows as separate windows managed under MDI parent window. However I have kept floating windows contained strictly within parent window as I intend to port this code to Silverlight soon.

I have also hosted this project on codeplex at: http://mixmodessynergy.codeplex.com/ 

Anatomy of Synergy Toolkit  

 

MixModes Synergy toolkit consists of following top level projects: 

  • MixModes.Synergy.Resources – Contains image and language resources
  • MixModes.Synergy.Themes – Defines default themes for user controls, custom windows, colors, brushes and text themes
  • MixModes.Synergy.Utilities – Common utilities
  • MixModes.Synergy.VisualFramework – Contains behaviors, adorners, commands, user controls, docking framework and other WPF specific functionality
  • Synergy – Sample project highlighting features of the toolkit

Anatomy of Dockable Window 

To understand window docking solution, it is necessary to understand the very primitive control that drives it all – the dockable window. A dockable window is a special window that in addition to content and title can be in following several states: 

1. Pinned state – Dockable window can be pinned to the side of parent window to have consistent visibility. Usually frequently used content is pinned for easier and constant access. 

1.png  

2. Auto hidden state – Less frequently used windows can be auto hidden so when mouse is not hovering over them they collapse into a condensed form (which I refer to as header). When mouse hovers over the headers, full window slides out from the docked side.

2.png 

3. Document state – When used as a document, dockable windows can merge as tab items within a tab control. 

3.png  

4. Floating – Usual floating windows 

4.png

 

Docking window support is provided by DockPane control that is derivative of HeaderedContentControl. In addition to Header and Content properties that it inherits from HeaderedContentControl, it also contains the following properties:

  • Icon – Icon for the docked window
  • CondencedDockPanelTemplate – Template for condensed DockPane
  • CondencedDockPanel – Condenced form of DockPane
  • DockPaneState – State of the dock pane

DockPane also contains the following events:

  • Close – Close event
  • TogglePin – Toggling of pin / auto-hide button
  • HeaderDrag – Notification that user has started to drag header (title) of the DockPane

Default theme for DockPane is defined in the DockPane.xaml resource dictionary within MixModes.Synergy.Themes project.

Document Containers 

DockPane(s) are contained within document containers. Document container is modeled via DocumentContainer class which is derivative of ContentControl. DocumentContainer can be in one of the following (mutually exclusive) states:

  • Empty – DocumentContainer does not contain any DockPane
  • ContainsDocuments – DocumentContainer contains one or more DockPane(s) as documents
  • SplitHorizontally – DocumentContainer is split horizontally 
  • SplitVertically - DocumentContainer is split vertically  

5.png  

DocumentContainer is complex in terms of its template since it is required to represent either a split view or a tabbed view. I have used a persistent TabControl within the template that is hidden if Content property is ever non-null. Content of-course is used exclusively to contain split views via Grid with two children DocumentContainer. 

DocumentContainer contains the following properties: 

  • State – State of the DocumentContainer 
  • Documents – contains documents represented in TabControl 
  • DocumentsTab – TabControl containing documents 
  • DockIllustrationPanel – This is a panel which contains docking illustration points indicating to the user where a content should be docked. If user drags a DockPane to any one of these points it gets docked at the respective position. The image below shows content of the DockIllustrationPanel:

6.png

 

DocumentContainer contains the following methods:

  • AddDockPane(DockPane, ContentDockPoint) – Adds a DockPane as a docked window
  • AddDocumentContainers(IEnumerable<DocumentContainer>, bool) – Splits and add child DocumentContainers
  • AddDocument(DockPane) – Adds a DockPane as a tabbed document
  • RemoveDocument(DockPane) – Removes a DockPane as a tabbed document

The template of DocumentContainer contains the following visuals in layers (bottom visuals are in increasing Z-Order): 

  • TabControl (PART_DOCUMENTS) – Bound to Documents property of DocumentContainer
  • ContentPresenter – This is where split children are added
  • Grid (PART_DOCK_POINTS) – Panel for hosting dock illustration points
  • Grid (PART_DOCK_ILLUSTRATION) – DockIllustrationPanel for illustrating future docking via cues  

 

Windows Manager

Windows manager is the component that binds DockPanel(s) and DocumentContainer(s) together to provide window management functionality in applications. In addition, window manager contains auto-hide and pinned dock points on all four window sides so DockPane(s) can be pinned or auto hidden outside of the DocumentContainer(s). WindowsManager also contains the root DocumentContainer that can host documents in tab control or nested-split DocumentContainer instances hosting documents.

WindowsManager has the following properties:

  • DockPaneIllustrationStyle – Illustration for docking a window within WindowsManager or DocumentsContainer
  • DockIllustrationContentStyle – Illustration for merging documents while dragging a DockPane in TabControl
  • ActiveWindowsManager – Static property indicating a WindowsManager undergoing the drag operation
  • DraggedPane – DockPane that is being dragged
  • <Orientation>WindowHeaders – StackPanel containing condensed auto-hidden DockPane(s)
  • <Orientation>PinnedWindows – DockPanel containing pinned DockPane(s)
  • DocumentContainer – Root document container
  • DockingIllustrationPanel – Docking illustration panel for future pinned DockPanel(s)
  • PopupArea – DockPanel where auto-hidden DockPane(s) slide out when mouse hovers upon the condensed headers
  • FloatingPanel – Canvas that contains floating DockPane(s)
  • DockingPanel – DockPanel that contains dock points for pinned windows as shown in image below: 

7.png 

WindowsManager has the following methods:  

  • AddPinnedWindow(DockPane, Dock) – Adds a pinned DockPane
  • AddAutoHideWindow(DockPane, Dock) – Adds an auto-hidden DockPane
  • AddFloatingWindow(DockPane) – Adds a floating DockPane
  • RemoveDockPane(DockPane) – Removes a DockPane from (pinned, auto-hidden or floating portion of ) WindowsManager
  • Clear – Clears the WindowManager of all DockPane(s)
  • StartDockPaneStateChangeDetection – Starts state monitoring for DraggedPane
  • StopDockPaneStateChangeDetection – Stops state monitoring for DraggedPane  

How it all works together

 

The image below illustrates the relationship between various components of the docking solution:

8.png

Structurally WindowsManager is the encompassing component that contains pinned and auto-hidden DockPane(s). It also contains the root DocumentContainer. DocumentContainer on the other hand can either contain documents in the tab control by wrapping DockPane within DocumentContent instance or it can contain split windows where a grid holds child DocumentContainer(s) each of which recursively can either contain documents or further child DocumentContainer(s).

WindowsManager constantly monitors the state change of DockPane. When a DockPane drag is detected, it is placed on the FloatingPanel canvas of WindowsManager as a floating window that can be dragged around. During a drag of a DockPane hit testing is turned off on the DockPane so mouse events can flow down to controls below it such as WindowsManager and DocumentContainer.

For orchestrating drag and drop and docking functionality I have used a behavior driven approach. The idea is to expose functional end-points (such as methods and properties) on visual entities such as DockPane, DocumentContainer and WindowsManager and use behaviors to orchestrate and call these functional end-points. This approach also resulted in manageable-encapsulated components that were easier to trace and test.

DockPointBehavior monitors the dragged DockPane over WindowsManager and pops up dock points for pinning it. ContentPointBehavior on the other hand injects similar functionality within DocumentContainer for splitting and tab merging purpose.

Both WindowsManager and DocumentContainer have dock illustration grid containing the docking behavior. DockPointBehavior behavior illustrates pinned docking illustration on  WindowsManager whereas ContentDockBehavior illustrates splitting and tab merging illustration on DocumentContainer.

Using the code 

Using WindowsManager is extremely simple: 

  • Import the namespace in the xaml 
     Collapse
    xmlns:visualFx="http://mixmodes.com/visualFx"  
  • Drop the WindowsManager in the xaml 
     Collapse
    <visualFx:WindowsManager x:Name="WindowsManager"/>  
  • Start creating DockPane and insert them within WindowsManager / DocumentContainer 
     Collapse
    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); 

Serializing Window State 

Out of the box, Synergy provides XML serialization of window states through XmlWindowsManagerSerializer and XmlWindowsManagerDeserializer classes. Custom serialization is supported via specialization of base classes WindowsManagerSerializer and WindowsManagerDeserializer respectively.

XML serialization of WindowsManager using XmlWindowsManagerSerializer requires two pieces of information during construction:

  • DockPane writer – An Action<XmlElement, DockPane> instance that can write additional metadata about a DockPane to the XmlElement.

  • Document writer – A Func<DocumentContent, string> instance that takes in a DocumentContent and returns a string representation of the content. Note: DocumentContent.DockPane property returns the associated DockPane, however the Header and Content properties of DockPane are set to null. To access Header and Content property, use the Header and Content properties of DocumentContent instance directly.

Once XmlWindowsManagerSerializer instance is created, a call to Serialize(Stream, WindowsManager) method serializes WindowsManager to the stream.

Similar to serialization process, deserialization process requires an instance of Action<DockPane, string> within the constructor of XmlWindowsManagerDeserializer to de-serialize a DockPane from previously saved string representation. Deserialization does not require additional Action to realize DocumentContent since DocumentContent is inherently a serialization wrapper for DockPane.

Once XmlWindowsManagerDeserializer  instance is created, a call to Deserialize(Stream, WindowsManager) deserializes the WindowsManager to previously saved state.

All the docking functionality can be exercised by using the sample app (Synergy project) with the source code. Any comments or suggestions or bug-reports are as usual always welcome. Happy coding ! 

 

 

 

 

 

Screenshot - ScrShot1.gif

 

Introduction

Recently, I started a project for porting a Windows Forms application to WPF. I was quite a novice in WPF, so at first I was considering using some type of Windows Forms interoperability. In particular, the WinForm application has cool docking functionalities that I wanted to port to a newer version. As I was going deeper into WPF technology, I discovered a new world that radically changed my initial ideas. In this article, I wish to share a library that implements the Windows docking feature in pure WPF without any Win32-interop.

Using the code

There are three fundamental classes: DockManagerDockableContent and DocumentContentDockManager is a class responsible for managing the main window layout. Pane represents the window area that can be docked to a border. Each Pane contains one or more ManagedContentelements, which precisely refer to a window content element of the client code. Using this library is straightforward. DockManager is a user control that can be easily embedded into a window. For example, the following XAML code creates a DockManager in a DockPanel:

 Collapse
<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>

Notice that to use DockManager you have to refer an external CLR namespace, DockingLibary here. You can now add your windows toDockManager with code like this:

 Collapse
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();
    }
} 

Ib order to be dockable, a window must derive from DockableContent. Deriving from DockableContent indicates that the window content can be hosted in DockablePane. Windows are initially docked either where you set the Show member call or, by default, to the left border. The last thing to see is how to add a document window. A document window is a particular window that can't be docked to the main window border. It lives only inDocumentsPane which, as DockablePane, is a particular kind of Pane. The following code adds a document window with a unique title toDockManager:

 Collapse
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);
}

Points of interest

Exactly how is docking realized? I implement a simple algorithm here to manage relations between windows that are docked. DockingGrid contains code to organize Pane in a logical tree. DockManager calls DockingGrid.ArrangeLayout in order to organize the window layout. You also useDockingGrid.ChangeDock when you need to dock a Pane to a main window border. The following image shows a logical tree of panes. Don't get confused with the WPF logical tree.

Screenshot - DockingLibTree.jpg

Each node is a group of either two Panes or a Pane and a child. To arrange the layout, DockingGrid creates a WPF grid for each group with two columns or two rows, depending on split orientation. I hope the image is self-explanatory. Anyway, feel free to ask for more details if you are interested.

From version 0.1, the library supports floating windows, as you can see in VS. A floating window is an instance of the FloatingWindow class. It' s a very particular window because it needs to "float" between two windows. One is the parent window, MainWindow in this case, which is usually the main application window. The other is a transparent window owned by FloatingWindow itself, which is shown during dragging operations.

Screenshot - ScrShot2.gif

As you can see in the previous image, FloatingWindow supports useful switching options through a context menu on the title bar. In WinForms, controlling a non-client window area means overriding WndProc and managing relevant messages like WM_NCMOUSEMOVE.

In WPF, we have no access to messages posted to a window. This is because everything is controlled by the WPF engine that draws window content, fires keyboard and mouse events and does a lot of other things. The only way I know of to intercept messages is to install HwndSourceHook with a delegate to our functions. The following code shows how to manage a WM_NCLBUTTONDOWN message:

 Collapse
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;
        }
    }
}

 

相关文章推荐

WPF-Docking-Library 实现类似Visual Studio浮动窗口效果

http://www.codeproject.com/Articles/18812/WPF-Docking-Library
  • newhj
  • newhj
  • 2014年05月23日 10:01
  • 1889

WinForm界面开发之布局控件"WeifenLuo.WinFormsUI.Docking"的使用

WinForm界面开发之布局控件"WeifenLuo.WinFormsUI.Docking"的使用 本篇介绍Winform程序开发中的布局界面的设计,介绍...

VC6创建single document 不勾选 Docking toolbar 或Initial status bar崩溃问题

VC6创建single document 不勾选 Docking toolbar 或Initial status bar崩溃问题        2012-03-05 17:0354人阅读评论(0...

ALV OO: 最简单的ALV OO实例-Docking容器

最简单的ALV OO实例,Docking容器实现,ALV宽度可以由用户自动调整……

【WeifenLuo.WinFormsUI.Docking】的个人小结,逐步完善中

以下是两种窗口的停靠效果: //效果1:子窗体1和子窗体2以选项卡方式停靠在左侧; Frm_Child1 = new Frm_Child1(); Fr...

界面布局控件---WeifenLuo.WinFormsUI.Docking

这里我就简单的下一下他的实现步骤,当中我也看考了其他人的博客。  1) 创建一个windows Form的应用程序 2) 添加引用:WeifenLuo.WinFormsUI.Docking 他是...

C#.NET 窗体停靠控件WeifenLuo.WinFormsUI.Docking.dll的使用

介绍C#.NET的窗体停靠控件WeifenLuo.WinFormsUI.Docking.dll的使用,涉及子窗体的停靠、界面布局的保存等内容。...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:WPF Docking Library
举报原因:
原因补充:

(最多只允许输入30个字)