AvalonDock 1.3 Getting Started Tutorial

Thank you for your interest in AvalonDock. This tutorial refers to AvalonDock version 1.3 which targets .NET4. Even if many concepts are related to latest release, you can use this article as general guide to any older version.

AvalonDock can be used to build sophisticated interfaces like VisualStudio or Expression Blend. It has features like autohide panes, floating panes, save/restore layout and also it can host WinForms controls.

Main menu:

To correctly understand how AvalonDock works, let's start describing its components:

ADTutorial.png

  • DockingManager This is the core control in AvalonDock. It arranges contained panes, handles fly out panes and floating windows. In the above image the DockingManager object contains everything (in WPF terms) from the toolbar on top and the status bar on bottom. DockingManager class also handles saving and restoring layout.
  • ResizingPanel This panel arranges children panes along a direction (selected with property Orientation) adding a resizer between them. Above a ResizingPanel with horizontal orientation arranges three panes: a DockablePane on left, a DocumentPane in the central area, and a DockablePane on right.
  • DockablePane This control (an ItemsControl) contains a collection of ManagedContent obiects. Usually it arranges contains contents like a tabcontrol. In the above screenshot DockablePanes are the container of contents 'Strumenti' and 'Progetti' (Tools and Projects in English) on the left and the container of 'Classi' and 'Proprieta'' (Classes and Properties in English) on the right. A DockablePane can be auto-hidden (like that containing contents 'Errori'(errors), 'Lista Azioni'(action list) and 'Uscita'(output)) and can be dragged over the DockingManager as floating window or anchored to a border of the parent DockingManager.
  • DocumentPane A pane of this type contains usually documents (object of type DocumentContent) but optionally can also contain DockableContents object like the above 'Tools' or 'Classes' contents. Above a document content is placed inside a ResizingPanel (horizontally orienteted) in the central area between the too DockablePane object just mentioned. A document pane can't be moved.
  • DockableContent A dockable content is the container of application controls. It's always contained in a pane (DockablePane or DocumentPane). In the above screen shot, DockableContent objects are the 'classi'(classes) object (which contains a SharpDevelop object), the 'strumenti'(tools) object but also the 'Errori' (errors) (which is in the AutoHidden state and is contained in a autohidden pane). A DockableContent as name suggests can be dragged away from its container pane and be repositioned into another esisting pane, or to a border of the parent DockingManager or left in a floating window.
  • DocumentContent is content that can be hosted only in a DocumentPane. It's a particular content because can't be anchored to a border but can be positioned only into a DocumentPane. In latest release of AvalonDock, a DocumentContent can be also dragged as floating window but when relocated into the parent DockingManager it must be always placed inside a DocumentContent. Above DocumentContent objects are the 'program.cs' or 'MainForm.cs' files.
  • FloatingWindow It's a window that contains contents when are dragged or moved over a DockingManager. A FloatingWindow derives from Window, and always contains a pane (DockablePane or DocumentPane) which in turn contains one or more contents (DockableContent or DocumentContent). FloatingWindow object is always created directly from the DockingManager when user start a dragging operation for a content or a DockablePane.
  • Pane It's a base class for DockablePane and DocumentPane. It provides common properties and methods for both.
  • ManagedContent It's a base class for DockableContent and DocumentContent. It provides common properties and methods for both.

Now let's make our first AvalonDock application. Create a new Visual Studio 2010/SharpDevelop 4 application. If you have installed AvalonDock with the installer, you should already see AvavlonDock controls inside the Tools pane of your environment, if not no problem just add a reference to AvalonDock assemblies (AvalonDock.dll and AvalonDock.Themes.dll).
First thing to do is to add a DockingManager control:

<Window x:Class="AvalonDockSampleProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:ad="clr-namespace:AvalonDock;assembly=AvalonDock"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="24"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="24"/>
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="File">
                <MenuItem Header="Exit"/>
            </MenuItem>
        </Menu>

        <ad:DockingManager x:Name="dockManager" Grid.Row="1"/>
        
        <StatusBar Grid.Row="2">
            <StatusBarItem Content="AvalonDock 1.3 Sample Project"/>
        </StatusBar>
    </Grid>
</Window>

I've also added a menu and a status bar just to 'prettify' the window :).
Now will start creating the layout adding a resizing panel with a DockablePane (with a DockableContent inside) and a DocumentPane (with a DocumentContent):

        <ad:DockingManager x:Name="dockManager" Grid.Row="1">
            <ad:ResizingPanel Orientation="Horizontal">
                <ad:DockablePane>
                    <ad:DockableContent x:Name="classesContent" Title="Classes"/>
                </ad:DockablePane>
                <ad:DocumentPane>
                    <ad:DocumentContent Title="MyDocument!"/>
                </ad:DocumentPane>
            </ad:ResizingPanel>
        </ad:DockingManager>

or in code:

public MainWindow()
{
    InitializeComponent();

    var resPanel = new ResizingPanel() { Orientation = Orientation.Horizontal };
    var dockPane = new DockablePane() { };
    var documentPane = new DocumentPane() { };

    dockPane.Items.Add(new DockableContent()
    {
        Name = "classesContent",
        Title = "Classes"
    });

    documentPane.Items.Add(new DocumentContent()
    {
        Title = "My Document!"
    });

    resPanel.Children.Add(dockPane);
    resPanel.Children.Add(documentPane);
    dockManager.Content = resPanel;
}

Please note that it's important to set the name of content. This because it's used when DockingManager load a stored layout to correctly identify contents. Without a name a content it's not restored. As you can see I set a name for the DockableContent (which I want to be correctly serialized) and not for the DocumentContent (that in the other case I don't want to save into layout).

Now if you run the application you should be able to move the resizer, autohide the pane, floating it and anchor to a border. The DocumentContent instead can be only moved over the main window and relocated where it was.

Now let's complicate a little bit the layout adding more contents and resizing panels in a tree-like model:

        <ad:DockingManager x:Name="dockManager" Grid.Row="1">
            <ad:ResizingPanel Orientation="Vertical">
                <ad:ResizingPanel Orientation="Horizontal">
                <ad:DockablePane ad:ResizingPanel.ResizeWidth="150">
                    <ad:DockableContent x:Name="classesContent" Title="Classes">
                        <TreeView>
                            <TreeViewItem Header="Class1"/>
                            <TreeViewItem Header="Class2"/>
                        </TreeView>
                    </ad:DockableContent>
                    <ad:DockableContent x:Name="toolsContent" Title="Tools">
                        <ListBox>
                            <ListBoxItem Content="Tool1"/>
                            <ListBoxItem Content="Tool2"/>
                            <ListBoxItem Content="Tool3"/>
                        </ListBox>
                    </ad:DockableContent>
                </ad:DockablePane>
                <ad:DocumentPane>
                    <ad:DocumentContent Title="MyDocument!"/>
                </ad:DocumentPane>
            </ad:ResizingPanel>
                <ad:DockablePane ad:ResizingPanel.ResizeHeight="100">
                    <ad:DockableContent Title="Errors" x:Name="errorsContent">
                        <ListView>
                            <ListView.View>
                                <GridView>
                                    <GridView.Columns>
                                        <GridViewColumn Header="Error"/>
                                        <GridViewColumn Header="Page"/>
                                        <GridViewColumn Header="File"/>
                                        <GridViewColumn Header="Row"/>
                                    </GridView.Columns>
                                </GridView>
                            </ListView.View>
                        </ListView>
                    </ad:DockableContent>
                    <ad:DockableContent Title="Output" x:Name="output">
                        <TextBox IsReadOnly="True" AcceptsReturn="True"/>
                    </ad:DockableContent>
                </ad:DockablePane>
            </ad:ResizingPanel>
        </ad:DockingManager>

Note that I've used a dependency property (ResizePanel.ResizingWidth/Height) to fix the size of the dockable pane. With this property of type (GridLenght) one can specify panel size and how it's resized when user drag the resizer just like happen in the standard Grid panel.

Now you should see something like this:
ADTutorial2.png

AvalonDock Contents

Now we'll see how to dynamically create contents (DocumentContent and DockableContent).
Both classes derive from an abstract class called ManagedContent which defines common properties and methods.

Creating a content is really simple:

DocumentContent:
var documentContent = new DocumentContent();
documentContent.Title = "MyNewContent";
documentContent.Show(dockManager);
DockableContent:
 var dockableContent = new DockableContent()
 {
    Name = "mynewContent",
    Title = "New Dock Content!"
 };

 dockableContent.Show(dockManager);

ManagedContent and its derived classes have some overloads of the function Show which can be used to properly show contents in specific position and states. Also DockableContent as well as DocumentContent has a some more methods that can be used to auto-hide parent pane or show it as floating window.
Of course one could also traverse the DockingManager content to find the right pane and add the new content to it. This method works too. In general anyway consider always to use standard methods like Show/Hide/Close to handle DockableContent/DocumentContent states. This because DockablePane/DocumentPane are created/destroyed as user move/anchor contents inside the DockingManager.

Following is shown a list of common methods that can be used against Contents ans Panes:

Method Description
DocumentContent.Show(DockingManager) Shows a document inside the main document pane of the provided DockingManager. In no manager is passed, be sure that document content is already contained in a DockingManager.
DocumentContent.Show(DockingManager, bool showAsFloatingWindow) Same as Show() method but last parameter offers calling code control whether or not new document is shown n a floating window.
DocumentContent.Close() Closes a document (OnClosing/OnClosed events are raised).
DocumnetContent.Hide() Same as Close() method.
DockableContent.Show(DockingManager) Shows a dockable content inside provided DockingManager. If no manager is passed be sure that content is already inside a DockingManager. If content is in Hidden state (i.e. content is hidden) this method shows it again locating it inside previous container pane. If that pane is no more available, a right pane is created on the fly. If content is in AutoHidden state, content is shown in auto-hidden pane shown as happens when user move mouse over an anchor tab.
DockableContent.Show(desideredAnchor) Shows a content anchoring it to a border of the container DockingManager. If a pane is found with the right anchor, DockingManager will add new content to it.
DockableContent.ShowAsDocument() Shows the content as document, i.e. it's added to the main document pane and its state is set to DockableContentState.Document
DockableContent.ShowAsFloatingWindow() Content is shown inside a floating window. Last parameter (dockableWindow flag) indicates if new window can be docked again to the parent DockingManager
DockableContent.Hide() The content is hidden. Its state is set to DockableContentState.Hidden. If this content is than shown again, DockingManager tries to add it the previous container pane (at the previous position)
DockableContent.Close(bool forceClose) If DockableContent.IsCloseable property is false (as default) content is not closed but only hidden (same effect as calling Hide() method). In the latter case it's removed from the controls tree and it's not more present under DockingManager.DockableContents list. If forceClose is true, IsCloseabled state is not considered.
DockableContent.ToggleAutoHide() If content is already in auto-hide state (DockableContentState.AutoHidden), it's docked to the parent container. If instead it's in docked state (DockableContentStated.Docked) it's added to fly-out pane and an anchor tab is created to the relative border.
ManagedContent.Activate() ManagedContent is the parent class of DockableContent and DocumentContent. This method select the content within the docking manager giving it the focus.


Following is the list of state that a DockableContent can have (DocumentContent has not a similar property):

State Description
DockableContentState.None Content is not associated with any DockingManager.
DockableContentState.Docked Content is placed inside a pane within a DockingManager. This state is the most common.
DockableContenState.AutoHide Content is inside a pane that can be shown on the fly moving mouse over a small label (anchor tab) placed to a border of the DockingManager.
DockableContentState.DockableWindow Content is inside a pane that in turn is inside a floating window. IN this state a content can be docked to a border or inside a pane of the docking manager (i.e. anchors are shown as user move the window).
DockableContentState.FloatingWindow Content is inside a pane and a floating window that can't be docked to parent DockingManager (i.e. it's a normal window)
DockableContentState.Document Content is contained in a DocumentPane.
DockableContentState.Hidden Content is hidden but always present in DockingManager.DockableContents collection (NOTE: this behavior is specific to version 1.3).


Layout Serialization

Last task for this first tutorial is to add a serialization support for docking layout. Save a layout, simply means calling method DockingManager.SaveLayout() and restoring is also easy as calling another method RestoreLayout().

const string LayoutFileName = "SampleLayout.xml";

private void SaveLayout(object sender, RoutedEventArgs e)
{
    dockManager.SaveLayout(LayoutFileName);
}

private void RestoreLayout(object sender, RoutedEventArgs e)
{
    if (File.Exists(LayoutFileName))
        dockManager.RestoreLayout(LayoutFileName);
}

Even if it's easy to save/restore layout, you need to pay attention to one or two aspects.
First you need to specify the Name property for each content you need to save/restore.
Also you should know how AvalonDock handles some specific cases.
Consider for example that you start your application loading an initial layout containing only one content. After that your user with some commands add a new content (often this is the case when application is able to load at runtime an addin). When user close the application, your code call DockingManager.SaveLayout() to save current layout in order to recreate it when application is launched again and AvalonDock correctly stroe two contents (one that was present in the initial startup and one created at runtime from user). Now your user restarts the application, what happens when AvalonDock find a content that is not currently present in its layout? For this case you need to handle a specific callback method:
DockingManager.DeserializationCallback(DeserializationCallbackEventArgs);
This method is called whenever the DockingManager is not able to find the content to restore. If you recall, in our sample we added a function to add a dockable content at runtime (called ' mynewContent').
Following handles that callback method and return a new dockable content (of course in your case you can recreate on the fly what you need like the addin):
dockManager.DeserializationCallback += (s, e) =>
{
   //asserts that content that docking manager is looking for
   //is mynewContent
   Debug.Assert(e.Name == "mynewContent");

   var dockableContent = new DockableContent()
   {
       Name = "mynewContent",
       Title = "New Dock Content!"
   };

   //returns this new content 
   e.Content = dockableContent;
};

Again consider the case you want (always in the example of add-ins management described above) that you want to give your user the ability to switch at runtime between different layout one that contains only one content (the initial) and one (the current) that contains two contents (including the addin content). If you changes layout from initial layout to add-in layout the callback above is called and you have the chance to create dynamically your add-in. When you instead restore the initial layout, AvalonDock restore only one content layout (that it's present in the initial layout). What happens to the add-in content? It's hidden! This means that you just need to call its DockableContent.Show method to show it again.

Happy coding with AvalonDock!


Sample Project Sources

Last edited May 28, 2010 at 1:21 PM by adospace, version 19

Comments

galatei Jul 31, 2013 at 9:38 PM 
@Boinst It's clearly stated that this control is for WPF .

LordCshart Jul 5, 2012 at 1:12 PM 
при добавлении documentContent панели из нативного кода после сохранения не хочет снова востанавливать??! не подскажите

Boinst May 19, 2011 at 4:38 AM 
Very helpful tutorial! I struggled to get the library to work under Windows.Forms, but it works fine under WPF :D