This article presents a WPF docking window manager for Windows desktop applications. It allows the creation of a UI with the look and feel of widely used applications such as Microsoft Visual Studio with support for themes and layout serialisation.
Introduction
Some years ago, I created a commercial application with a large number of documents windows and tool windows. I wanted the user to be able to arrange the windows in the most convenient manner for them as per Microsoft Visual Studio. I chose to build the application around the free version of AvalonDock. I chose AvalonDock because it was free and it had a good reputation. I learnt that it is indeed a very good control, with an extremely powerful and flexible UI. I also learnt that it can be very hard to customise as it is a complex piece of software. I had requests from users to add or change features, and quite often, I was unable to figure out a way to make these changes. The most common request was to improve the tab control when multiple tools were docked together in a group. We had so many tool panes that it could be very hard to figure out which tab was which. We also had requests to add a close button to each tabbed tool or document pane. I found some solutions online, but quite often I couldn't get them to work. It is possible that the solutions were for an earlier version of AvalonDock.
Recently, I have had some spare time available, and I decided to create my own docking window manager library with the following key design goals:
- It should allow the creation of applications with the look and feel of class leading applications such as Microsoft Visual Studio.
- It should allow the developer to easily customise the appearance using themes.
- The code should be reasonably simple so that a developer can modify it to suit their own needs if the built in behaviour is inadequate.
- It should be fully compliant with the MVVM pattern.
- It should allow the layout to be serialised to and from a file.
Key features of the docking window control are as follows:
- Support for documents and tools.
- Documents represent editable content such as a text document, or the output from a calculation such as a 3D surface plot.
- Tools represent a collection of controls that in some way influence the content of documents, e.g., settings for a 3D surface plot.
- Documents can be laid out in a tiled pattern. Two or more documents can be grouped together into a tabbed group. The documents occupy a rectangular region (the document area).
- Tools can be laid out in a tiled pattern. Two or more tools can be grouped together into a tabbed group. The tools occupy the regions on either side of the document area.
- A tool can be unpinned, such that the pane is removed from the tiled area, and replaced with a title bar in the margin.
- Clicking on the title bar of an unpinned tool shows the previously hidden content. Clicking outside of the content hides it again.
- An unpinned tool can be re-pinned by clicking on the pin button in its header.
- Both documents and tools can be floated. They can be floated individually or as a tabbed group.
- The tools or documents in a tabbed group can be reordered by dragging each tab header.
- A tabbed group has a drop down menu item with a list of all documents/tools, allowing the user to quickly select the desired content.
- A tool and a document can be closed by clicking on a close button in the tab header. This will prompt the user to save unsaved data, discard unsaved data, or cancel the operation, assuming there is unsaved data.
- The control maintains a documents view model list and a tools view model list.
- There is a one to one correspondance between views and view models. Thus removing a view model will automatically close the associated view. Conversely, adding a view model will automatically display the associated view.
The following can be customized:
- The style for the side tool bars (which display the titles for unpinned tools)
- The styles for the selected and unselected tab headers
- The styles for all buttons
- All fonts
- The scroll arrows in tabbed groups
- The title bar in tool panes
- The border around each tool and each document
Note that the buttons in the example applications are defined using paths rather than bitmaps (or similar), which makes it trivial to change the colours and sizes to suit the theme.
I have called the component the Open Controls Dock Manager, 'open' indicating that the control is free. This article has an accompanying zip file containing the source code and a couple of example applications demonstrating how to use the library and create themes.
Example Applications
The first example application uses a theme that emulates Visual Studio 2109 as follows:
The main window view model has five documents and five tools. The five documents are grouped together in one pane. Of the five tools, two are unpinned on the right hand side of the UI, one is docked to the left of the documents, and two are docked as a group below the documents.
The second example application uses a different theme that I have called modern as follows:
A given document or view can be closed by clicking on the close button icon in the pane, or by means of the Documents and Tools application menus.
An unpinned tool pane can be displayed by clicking on the item in the margin:
The unpinned tool pane (and other tool panes in the same group) can be added back into the dock pane layout by clicking on the pin icon in the header bar.
A group of tools can be floated by dragging the header bar:
The small window icons at each side of the main window are location indicators. To insert the floating tool pane group back into the dock pane layout, drag the window until the cursor is over the appropriate location indicator. As the location indicators are defined in the theme, they are fully customisable.
Similarly, an individual tool (or document) pane can be floated by dragging its tab header.
A tool, or document, can be separated from a floating pane containing multiple tools, or document, in the same manner so that it becomes a floating pane containing one view.
Each pane group has a file list button which displays a list of the tools/document in the group allowing the user to quickly select the desired item. This is particularly useful when the group contains a large number of tools/documents. For example:
The tab order can be changed by dragging an item into a new location:
Running the Demo Applications
Start a demo application running. Click on the Window drop down menu (top right) and then click on the Load Layout menu item:
Choose the desired layout file. There are five sample layouts in the top level source code folder:
Background
In order to use the library, you will need to understand how to create WPF applications using the MVVM pattern and have a good understanding of C#. You will not need an in depth understanding of WPF although that will do no harm.
Using the Code
The main window XAML for each example application contains an instance of the LayoutManager
class as follows:
<dockManager:LayoutManager x:Name="_layoutManager"
Grid.Row="1" Grid.Column="0" DocumentsSource="{Binding Documents}"
ToolsSource="{Binding Tools}" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" Background="Gray" >
<dockManager:LayoutManager.Theme>
<themes:ModernTheme/>
</dockManager:LayoutManager.Theme>
<dockManager:LayoutManager.DocumentTemplates>
<DataTemplate DataType="{x:Type viewModel:DocumentOneViewModel}">
<view:DocumentOneView x:Name="_documentOneView"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:DocumentTwoViewModel}">
<view:DocumentTwoView x:Name="_documentTwoView"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:DocumentThreeViewModel}">
<view:DocumentThreeView x:Name="_documentThreeView"/>
</DataTemplate>
</dockManager:LayoutManager.DocumentTemplates>
<dockManager:LayoutManager.ToolTemplates>
<DataTemplate DataType="{x:Type viewModel:ToolOneViewModel}">
<view:ToolOneView x:Name="_toolOneView"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ToolTwoViewModel}">
<view:ToolTwoView x:Name="_toolTwoView"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ToolThreeViewModel}">
<view:ToolThreeView x:Name="_toolThreeView"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ToolFourViewModel}">
<view:ToolFourView x:Name="_toolFourView"/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ToolFiveViewModel}">
<view:ToolFiveView x:Name="_toolFiveView"/>
</DataTemplate>
</dockManager:LayoutManager.ToolTemplates>
</dockManager:LayoutManager>
The DocumentsSource
property is bound to the Documents
property on the view model. This is an observable collection of view models, each derived from the IViewModel
class.
The ToolsSource
property is bound to the Tools
property on the view model. This is an observable collection of view models, each derived from the IViewModel
class.
The LayoutManager.DocumentTemplates
property contains a list of data templates for documents, each defining the mapping between a view model and the corresponding view. Thus the DocumentOneViewModel
view model class provides the data displayed by the DocumentOneView
view class.
The LayoutManager.ToolTemplates
property contains a list of data templates for tools, each defining the mapping between a view model and the corresponding view. Thus the ToolOneViewModel
view model class provides the data displayed by the ToolOneView
view class.
There is a direct one to one correspondance between a tool view and a tool view model, and there can only be one instance of a given tool view. The Name
property is essential as it identifies the view model when serialising the view to and from file. The value of the Name
property must be unique.
In contrast, there may be multiple instances of a document view model, and hence multiple instances of the view. Each instance is distinguished by a unique URI, which could, for example, be the file path for the data, e.g., C:\Data\Document1.txt.
The appearance of the UI is set using a theme which consists of a dictionary defining styles, and a class derived from the Theme
class which provides the URI for the dictionary. For example:
public class ModernTheme : Theme
{
public override Uri Uri
{
get
{
return new Uri
("/OpenControls.Wpf.DockManager.Themes.Modern;component/Dictionary.xaml",
UriKind.Relative);
}
}
}
The theme class implements one method called Uri
which returns the Uri for the Theme
dictionary.
The Theme
is set in the LayoutManager.Theme
property as per the following example:
<dockManager:LayoutManager.Theme>
<themes:ModernTheme/>
</dockManager:LayoutManager.Theme>
The Theme
cannot be changed while the application is running.
Dictionary styles are for the most part not optional and a failure to include a required style will cause the application to crash.
The application needs to add a reference to the theme assembly, assuming it is external. The theme can, of course, be implemented in the application assembly. The main application window must reference the namespaces for the docking manager and the theme. For example:
xmlns:dockManager="clr-namespace:OpenControls.Wpf.DockManager;
assembly=OpenControls.Wpf.DockManager"
xmlns:themes="clr-namespace:OpenControls.Wpf.DockManager.Themes;
assembly=OpenControls.Wpf.DockManager.Themes.Modern"
Anyone familiar with AvalonDock will realise that I have used the same Theme
mechanism.
As explained above, the application provides a list of view model instances for the documents and the tools. Each view model class must implement the IViewModel
interface which is defined as follows:
public interface IViewModel
{
string Title { get; }
string Tooltip { get; }
string URL { get; }
bool CanClose { get; }
bool HasChanged { get; }
void Save();
}
The Title
property returns a string
that is displayed in the tab header.
The Tooltip
property returns a string
that is displayed as a tooltip for the tab header.
The URL
property is only used by document view models, not by tools. It returns the URL for the document, which will be the file path in most cases.
The CanClose
property returns true
if the document/tool can be closed and false
otherwise.
The HasChanged
property returns true
if the document has unsaved changes and false
otherwise. This property is only valid for documents.
The Save
method is invoked by the system to save the data associated with the document.
Architecture
The key classes and their relationships are illustrated by the following UML diagram:
The layout management logic is divided over a small number of key components, each with its own responsibilities, each interacting with others via well defined interfaces using dependency injection. This reduces coupling and dependencies, making it easier to maintain and extend the code.
The LayoutManager
class is derived from the Grid
control class, and it is the top level of a binary tree of documents and tools. Each node in the tree may be a splitter pane, or a document pane group or a tool pane group. A splitter pane is implemented by the SplitterPane
class, and it represents two nodes (which may be tool pane groups for example) separated by a GridSplitter
. Thus it represent a leaf in a binary tree. The grid splitter may be horizontal or vertical.
The DockPaneManager
class manages the layout of docked panes. It interacts with the LayoutManager
instance by means of the IDockPaneHost
and ILayoutFactory
interfaces. The latter interface provides services to instantiate nodes in the document/tool tree including document pane groups, and tool pane groups.
The DocumentPaneGroup
and ToolPaneGroup
classes represent the document and tool pane groups respectively. Each contains an instance of a class derived from the IViewContainer
interface which is responsible for the view content. Thus the ToolContainer
class displays one or more tool views and the DocumentContainer
class displays one or more document views.
The FloatingPaneManager
class manages the floating panes. It interacts with the LayoutManager
instance by means of the IFloatingPaneHost
and ILayoutFactory
interfaces.
The FloatingDocumentPaneGroup
and FloatingToolPaneGroup
classes represent the floating document and tool pane groups respectively. Each contains an instance of a class derived from the IViewContainer
interface.
The UnpinnedToolManager
class manages unpinned tools, i.e., tools that are shown as a title bar along one side of the docking area.
Example Layouts
The sample code includes five example layouts in the top level folder.
GitHub repository
The code is also available from a GitHub repository. Please note that the code is now part of the free open soruce OpenControls.Wpf project which includes many other useful WPF controls. The repository is here:
https://github.com/LeifUK/OpenControls.Wpf
I recommend that you get the code from the repository as it includes recent changes, specifically I have moved some functionality into separate shared libraries, and additional useful components.
History
- 19th June 2020: Version 1.
- 24th June 2020: Version 2, added a section explaining how to run the demo applications.
- 8th December 2020: Version 3, added support for the current, or active, document. Fixed a bug whereby the pane beneath a floating pane was highlighted when not dragging the floating pane.
- 11th December 2020: Version 4, fixed a bug in the serialisation code that was found and fixed by user SadE54. Many thanks. The ZIP file and GitHub repository have been updated accordingly.
- 2nd January 2021: Version 5: Created a new OpenControls.Wpf project which includes DockManager.