NOTE: This is not a tutorial on how to start using AvalonDock (see Getting Started Tutorial instead)

Introduction

In this document I'll describe how to manage WPF bindings for DockableContents and DocumentContents in AvalonDock.
When your users move contents over the DockingManager or set them as flyout windows, AvalonDock automatically creates new windows to host dragged contents. This was a project requirement in order to support WinForms control. In fact WPF always renders WinForms controls (the are internally child windows) always on top of WPF elements.
As you can imagine move contents to a different window breaks WPF binding. In the following sample I'll show you how we can solve the problem using binding to application level objects.

The Problem

Let's start creating a new application that uses AvalonDock (of course be sure to reference AvalonDock.dll assembly).

This is the XAML file:
<Window x:Class="AvalonDock.BindingSample.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="*"/>
        </Grid.RowDefinitions>
        <Menu>
            <MenuItem Header="Add Item" Click="OnAddItem"/>
        </Menu>
        <ad:DockingManager Grid.Row="1">
            <ad:ResizingPanel>
                <ad:DockablePane ad:ResizingPanel.ResizeWidth="100">
                    <ad:DockableContent Title="Sample Content">
                        <ListBox x:Name="listBoxToBind"/>
                    </ad:DockableContent>
                </ad:DockablePane>
                <ad:DocumentPane/>
            </ad:ResizingPanel>
        </ad:DockingManager>
    </Grid>
</Window>

Now let's add a collection of string as dependency property of the MainWindow which will be the binding source of the listbox “listBoxToBind” contained in the sample DockableContent shown above.

public static readonly DependencyProperty SampleCollectionProperty =
DependencyProperty.Register("SampleCollection", typeof(ObservableCollection<string>), typeof(MainWindow),
new FrameworkPropertyMetadata((ObservableCollection<string>)new ObservableCollection<string>()));

Than just add an event handler for the menu item present in the interface which simply add an item to the collection:

 public partial class MainWindow : Window
 {
     public MainWindow()
     {
         InitializeComponent();

         this.DataContext = this;

     }

     private void OnAddItem(object sender, RoutedEventArgs e)
     {
         SampleCollection.Add("NewItem " + (SampleCollection.Count + 1));
     }


     #region SampleCollection

     /// <summary>
     /// SampleCollection Dependency Property
     /// </summary>
     public static readonly DependencyProperty SampleCollectionProperty =
         DependencyProperty.Register("SampleCollection", typeof(ObservableCollection<string>), typeof(MainWindow),
                new FrameworkPropertyMetadata((ObservableCollection<string>)new ObservableCollection<string>()));

     /// <summary>
     /// Gets or sets the SampleCollection property. 
    /// </summary>
    public ObservableCollection<string> SampleCollection
    {
        get { return (ObservableCollection<string>)GetValue(SampleCollectionProperty); }
        set { SetValue(SampleCollectionProperty, value); }
    }

    #endregion
}

Finally bind the listbox to above collection (note that I set MainWindow data context to itself for semplicity) and run the application:

<ad:DockableContent Title="Sample Content">
   <ListBox x:Name="listBoxToBind" ItemsSource="{Binding SampleCollection}"/>
</ad:DockableContent>

Click on 'Add Item' menu you see something like the following screen-shot:

AD_Binding1.png

Everything seems to work fine but if you try to drag the dockable content away as floating window listbox items disappear. Same effect if you try to put the content as a flyout window. Moving back the content inside the docking manger you'll find that binding restart to work. As you can immagine binding breaks because content is moved to another window that have a different logical tree. datacontext of the main window is no more visible to the content dragged in this state.

The Solution

WPF give us many options to solve this small problem, of course one should adopt the solution that more suites its application requirement or design. In this case the simplest is to explicitly indicate a binding source that is visible at application level like the App.Current object.
Something like this:
<ListBox x:Name="listBoxToBind"
 ItemsSource="{Binding Path=MainWindow.SampleCollection, Source={x:Static local:App.Current}}"/>

Another workaround is creating a custom class deriving from DockableContent which exposes the object to bind and set the custom dockable content itself as DataContext for the listbox.


Command Bindings

You could experience problems in command bindings too. For an example try to add context menu to the list box with only one menu item. When user click on it, WPF executes a custom command that we want to handle with a method of the MainWindow class.
This is the command:
public class CustomCommands
{
    #region MyCommand

    /// <summary>
    /// The MyCommand command .
    /// </summary>
    public static RoutedUICommand MyCommand
        = new RoutedUICommand("My Command", "MyCommand", typeof(CustomCommands));

    #endregion
}
and this is the XAML modified with the command bindings for the MainWindow and the context menu attached to the listbox:
….
    <Window.CommandBindings>
        <CommandBinding Command="{x:Static local:CustomCommands.MyCommand}" 
CanExecute="OnCanExecuteMyCommand" 
Executed="OnExecutedMyCommand"/>
    </Window.CommandBindings><ListBox x:Name="listBoxToBind" 
ItemsSource="{Binding Path=MainWindow.SampleCollection, Source={x:Static local:App.Current}}">
      <ListBox.ContextMenu>
         <ContextMenu>
             <MenuItem Command="{x:Static local:CustomCommands.MyCommand}"/>
         </ContextMenu>
       </ListBox.ContextMenu>
</ListBox>

Finally we have to define two event handlers for the command binding that popup a message box:
 private void OnCanExecuteMyCommand(object sender, CanExecuteRoutedEventArgs e)
 {
     e.CanExecute = true;
 }

 private void OnExecutedMyCommand(object sender, ExecutedRoutedEventArgs e)
 {
     MessageBox.Show("My Command!");
 }

Now build the project and run the application. Whe content is docked to the DockingManager you should be able to right click on the listbox and then activate the menu item command that shows the dummy message box. Everything works fine until you don't drag the content as floating window or put it in a flyout window.
This is what happen:
AD_Binding2.png

Menu item is grayed because WPF can't locate a command binding able to handle the command traversing the logical tree up to the window containing the listbox. As in the first case binding stops working.
Solution here is even simpler: just move the command binding closer to the context menu. You can for example add the command binding to the DockableContent.CommandBindings:

<ad:DockableContent Title="Sample Content">
    <ad:DockableContent.CommandBindings>
       <CommandBinding Command="{x:Static local:CustomCommands.MyCommand}" 
CanExecute="OnCanExecuteMyCommand" 
Executed="OnExecutedMyCommand"/>
   </ad:DockableContent.CommandBindings>
   <ListBox x:Name="listBoxToBind" 
ItemsSource="{Binding Path=MainWindow.SampleCollection, Source={x:Static local:App.Current}}">
        <ListBox.ContextMenu>
             <ContextMenu>
                   <MenuItem Command="{x:Static local:CustomCommands.MyCommand}"/>
              </ContextMenu>
        </ListBox.ContextMenu>
    </ListBox>
</ad:DockableContent>

Now if you drag the content as floating window, command is correctly executed because WPF can find a command binding into the CommandBindings collection of the parent dockable content that is dragged.


Conclusion

In conclusion you should be aware that AvalonDock move contents to different windows as user drag them breaking any binding that relies on logical tree structure of the parent window. This of course could cause problems for any type binding like the link to a resource defined at window level (for example using the StaticResource markup expression).

This is the sample project:
AvalonDock.BindingSample.zip

Last edited Jul 22, 2010 at 10:09 AM by adospace, version 9

Comments

NicePoet Aug 18, 2012 at 10:24 AM 
There is more universal way to keep bindings.
http://avalondock.codeplex.com/workitem/15506

gandora Nov 4, 2010 at 4:02 PM 
First of all, I'd like to thank you for this article, it helped me out a lot for the project I'm currently working on. However, in the property binding section, you state "WPF give us many options to solve this small problem...", and that this solution is "the simplest is to explicitly indicate a binding source that is visible at application level like the App.Current object.". I was wondering what some of the other options are? Sometimes the simplest solution isn't always the best one for a given situation and having some options to weigh which is best for the situation is never a bad thing.

Thanks again,
Greg