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 the 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 the following several states:
- 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.
- 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.
- Document state – When used as a document, dockable windows can merge as tab items within a tab control.
- Floating – Usual floating windows
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
– Condensed 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 modelled 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
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-nu
ll. 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
:
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:
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:
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:
xmlns:visualFx="http://mixmodes.com/visualFx"
- Drop the
WindowsManager
in the XAML:
<visualFx:WindowsManager x:Name="WindowsManager"/>
- Start creating
DockPane
and insert them within WindowsManager
/ DocumentContainer
:
DockPane pane = new DockPane();
pane.Header = …
pane.Content = ….
WindowsManager.AddPinnedWindow(pane, Dock.Top);
WindowsManager.AddAutoHideWindow(pane, Dock.Left);
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!