DotNetSlackers: ASP.NET News for lazy Developers

Friday, January 7, 2011

Building a docking window management solution in WPF


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 a 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.
    image
  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.
    image
  3. Document state – When used as a document, dockable windows can merge as tab items within a tab control.
    image
  4. Floating – Usual floating windows
    image
Docking window support is provided by DockPane control that is a 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 modeled via DocumentContainer class which is derivative ofContentControlDocumentContainer 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
image
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 theDockIllustrationPanel:
    image
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:
    image
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

WindowsManager constantly monitors the state change of DockPane. When a DockPane drag is detected, it is placed on the FloatingPanelcanvas of WindowsManager as a floating window that can be dragged around. During a drag of a DockPane hit testing is turned off on theDockPane 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 DockPaneDocumentContainer 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.
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 onDocumentContainer.

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:
     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 andXmlWindowsManagerDeserializer classes. Custom serialization is supported via specialization of base classes WindowsManagerSerializerand 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 theXmlElement.
  • Document writer – A Func<DocumentContent, string> instance that takes in a DocumentContent and returns a string representation of the content.
    NoteDocumentContent.DockPane property returns the associated DockPane, however the Header and Content properties ofDockPane are set to null. To access Header and Content property, use the Header and Content properties of DocumentContentinstance directly.
Once XmlWindowsManagerSerializer instance is created, a call to Serialize(Stream, WindowsManager) method serializesWindowsManager to the stream.
Similar to serialization process, deserialization process requires an instance of Action<DockPane, string> within the constructor ofXmlWindowsManagerDeserializer 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 theWindowsManager 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!

No comments:

Post a Comment