Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A WPF Docking Windows Management Library

0.00/5 (No votes)
21 Jun 2020 1  
WPF docking window manager library
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:

  1. It should allow the creation of applications with the look and feel of class leading applications such as Microsoft Visual Studio.
  2. It should allow the developer to easily customise the appearance using themes.
  3. 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.
  4. It should be fully compliant with the MVVM pattern.
  5. It should allow the layout to be serialised to and from a file.

Key features of the docking window control are as follows:

  1. Support for documents and tools.
  2. Documents represent editable content such as a text document, or the output from a calculation such as a 3D surface plot.
  3. Tools represent a collection of controls that in some way influence the content of documents, e.g., settings for a 3D surface plot.
  4. 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).
  5. 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.
  6. A tool can be unpinned, such that the pane is removed from the tiled area, and replaced with a title bar in the margin.
  7. Clicking on the title bar of an unpinned tool shows the previously hidden content. Clicking outside of the content hides it again.
  8. An unpinned tool can be re-pinned by clicking on the pin button in its header.
  9. Both documents and tools can be floated. They can be floated individually or as a tabbed group.
  10. The tools or documents in a tabbed group can be reordered by dragging each tab header.
  11. 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.
  12. 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.
  13. The control maintains a documents view model list and a tools view model list.
  14. 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:

  1. The style for the side tool bars (which display the titles for unpinned tools)
  2. The styles for the selected and unselected tab headers
  3. The styles for all buttons
  4. All fonts
  5. The scroll arrows in tabbed groups
  6. The title bar in tool panes
  7. 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:

Image 1

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:

Image 2

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:

Image 3

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:

Image 4

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:

Image 5

The tab order can be changed by dragging an item into a new location:

Image 6

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:

Image 7

Choose the desired layout file. There are five sample layouts in the top level source code folder:

Image 8

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
{
    // A user friendly title
    string Title { get; }

    string Tooltip { get; }

    /*
     * Not used by tools.
     * Uniquely identifies a document instance.
     * For example a file path for a text document.
     */
    string URL { get; }

    bool CanClose { get; }

    /*
     * Return true if there are edits that need to be saved
     * Not used by Tool view model
     */
    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:

Image 9

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here