
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: DockManager
, DockableContent
and DocumentContent
. DockManager
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 ManagedContent
elements, 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
:
<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 to DockManager
with code like this:
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)
{
dockManager.ParentWindow = this;
propertyWindow.DockManager = dockManager;
propertyWindow.Show(Dock.Top);
explorerWindow.DockManager = dockManager;
explorerWindow.Show();
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 in DocumentsPane
which, as DockablePane
, is a particular kind of Pane
. The following code adds a document window with a unique title to 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);
}
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 use DockingGrid.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.

Each node is a group of either two Pane
s 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.

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:
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;
}
}
}
Although the delegate signature looks like a window procedure signature, it's not the same thing. You can, however, handle every message, even WM_PAINT
.
History
- 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