SandDock for WPF User Guide

Taking Control

Back to Table of Contents

Simple Programmatic Window Manipulation

Most of the time you will use a few simple methods to control the placement of the windows in your layout. Probably the most common methods to use are the Open and Close methods of a window. If you have a View menu allowing access to your windows, the handler for the menu items will simply call the Open method of the window it's designed to show. Because the user will be closing windows most of the time it may not be necessary to call Close programmatically, but in some situations you may wish for example to close all SandDock windows when the user closes your main application window. Close returns a boolean value indicating whether the close operation was successful. It will be unsuccessful if the Closing event was handled and cancelled.

When using Open and related positional methods you will sometimes need to pass a parameter of type WindowOpenMethod. There are three ways in which a window can be opened. The first, Background, will bring the window onscreen in as much as its tab will be added to a container, but it will not be brought to the front or activated. This is normally used in a document situation where a document needs to be opened in the background. The second, OpenSelect, brings the window onscreen and opens it (remember that a window may be collapsed in an "unpinned" state) but does not give focus to it. This is useful in situations where you want to ensure a window is visible (i.e. debugging output) but the keyboard focus should not be changed. The last, OpenSelectActivate, brings the window fully onscreen and activates it. Normally you will want to use the last option, and indeed we provide a parameterless Open method that defaults to that behaviour.

To programmatically change the position of a window we provide Dock, Document and Float methods. We also provide Dock and Float methods on WindowGroup for operating on an entire group of windows. All these methods take a parameter of type WindowOpenMethod as discussed in the last paragraph.

Advanced Programmatic Window Manipulation

One of the reasons you are using SandDock is of course because it is very easy to use, and has a simple API. In configuring your layout with XAML you will have been using a combination of SplitContainers and WindowGroups to achieve the perfect layout, and of course at runtime that could be changed by the user. It is important to realise that there is very fine-grained control over the layout of windows at runtime using the same objects. You can enumerate the SplitContainers collection of your DockSite and drill down into those to determine the type of layout currently configured. You can add and remove windows to and from existing WindowGroups by using their Items collections. The simple APIs exposed by the library are of course doing this under the hood.

We provide a Remove method on windows which does not close a window in the sense of raising Closing and Closed events, but removes it from the current layout, including all the logic to subsequently remove its group if the group no longer has any windows, then remove the group's parent if that has no more children, and so on. The Remove method is intended for situations where you will be programmatically adding the window to another location onscreen, and the simple Dock, Document and Float methods are insufficient.

To programmatically unpin a window, you must go through its parent WindowGroup. Cast the window's Parent to WindowGroup and set the Pinned property appropriately.

Window sizing is all proportional and based on a "working size" that is never actually achieved by any window but is used by layout managers to decide how large windows should be when compared with one another. Two windows with working sizes of 1000x2000 will appear exactly the same size in layout as if they were both 15x30. The working size is used by SplitContainers and is therefore applicable to SplitContainers and WindowGroups, those being the permissable child types. Let's say you have a layout with two windows docked on the right of your work area, called Solution Explorer and Properties (just like Visual Studio). By default there will be a splitter halfway down and the windows will be equal sizes. However, if you explicitly set the WorkingSize attached property (defined on SplitContainer) to 200x200 for the first and 200x400 for the second, the splitter will be a third of the way down because the bottom window is twice the height of the top.

A split container docked in a dock site stretches to fill the width (if docked to the top or bottom) or height (if docked to the left or right) of its container, and the extent of the other dimension is determined by the ContentSize attached property, defined on DockSite. So, when defining a window docked at the bottom of the working area, it will expand to fill its container (the SplitContainer) and the container will stretch horizontally and have its height set by the ContentSize property.

Windows also have a ContentSize property, which is different. When a window is unpinned and shown, its size in that situation is governed by its ContentSize. The user can of course change this size with a splitter.

Window Layout Serialization

A complete layout of windows is a potentially complex thing, with docked, floating and collapsed tool windows and documents in a tabbed hierarchy or an MDI container. We therefore expose simple methods to serialize the entire layout to a string, easily persistable to the storage medium of your choice.

As a prerequisite to this functionality, you must ensure each window has its own unique Guid property. For example, let's say your application has a Properties window. This window must always have the same value in its Guid property from one run of the application to another, otherwise SandDock has no way of identifying it (other than its title, which is clearly inadequate). This is very straightforward for windows where there will only ever be one instance (such as the Properties window) but less easy with, say, document windows or other windows where there will be multiple instances created and shown by your application at runtime. For the purposes of this section we will call these Dynamic Windows.

Use the GetLayout and SetLayout methods on your DockSite to implement layout serialization. The former has one parameter, includeDocuments, which indicates whether your documents should be included as part of the serialized information. If documents are not included, then when it comes time to deserialize the layout information your documents will not be touched, whereas every other window will be removed and reopened at its previous location.

If your application has dynamic windows it must take responsibility for recreating these windows upon the next session load, before asking SandDock to restore layout. SandDock is just a user interface library, it knows nothing about what your application does and cannot be responsible for loading your documents. You must therefore have your own application configuration file that can load all relevant windows that were open ready for SandDock to present them. Loading them is just a case of instantiating them and setting their DockSite and Guid properties.

You should call GetLayout in the Closing event of your application window and SetLayout in the Loaded event.

Loading windows can be expensive, and in a large application where nowhere near all available windows are visible on startup, you may wish to defer creating them until actually needed. The LoadWindow event on your DockSite is specifically for this purpose, and it fires whenever SandDock is trying to locate a window by its Guid but cannot find it. The handler for the event has a chance to load the window. It is suggested that in these large applications you keep a table of Guids and only ever refer to a window by its Guid, calling the FindWindow method to get the actual window instance from its Guid whenever you need to do anything with it. As long as you implement behaviour in your LoadWindow handler to actually create the window and hand it back to the event caller this pattern works very well. This level of complexity is not needed for most applications however, where windows can simply be defined in your main XAML file and referred to by name.

Next: Window Switching