Skip to main content

MVVM Support - Bind to a Collection of Dock Panels

  • 7 minutes to read

You can use any of the following ways to bind a LayoutGroup / DockLayoutManager to a collection of dock panels.

Populate DockLayoutManager

DockLayoutManager can display dock items which are stored in a ViewModel collection. In this case, DockLayoutManager should contain predefined target layout groups and you should know their names.

Target Group

Each DockLayoutManager’s dock panel has its target. Target is a layout group where the DockLayoutManager should place dock panels. A target also defines the type of a created dock panel.

Target Type Type of the Created Object
AutoHideGroup LayoutPanel
DocumentGroup DocumentPanel
FloatGroup LayoutPanel
LayoutGroup LayoutPanel
TabbedGroup LayoutPanel

Existing Dock Panels

In this approach, the ViewModel should implement the IMVVMDockingProperties interface. The TargetName property should return the name of the target layout group where DockLayoutManager places the created dock panel.

The following code sample populates DockLayoutManager with the ChildViews ViewModel collection:

public class MainViewModel : ViewModelBase {
        public ObservableCollection<PanelViewModel> Panels {
            get { return GetValue<ObservableCollection<PanelViewModel>>(); }
            set { SetValue(value); }
        }

        public MainViewModel() {
            Panels = new ObservableCollection<PanelViewModel>() {
                new PanelViewModel() { Caption = "One", Content = "A regular panel", TargetName = "documentGroup" },
                new PanelViewModel() { Caption = "Two", Content = "A regular panel", TargetName = "documentGroup" },
                new PanelViewModel() { Caption = "Three", Content = "A regular panel", TargetName = "documentGroup" },                
                new PanelViewModel() { Caption = "Five", Content = "A panel", TargetName = "layoutGroup" },
            };
        }
    }    

    public class PanelViewModel : ViewModelBase, IMVVMDockingProperties {
        public string Caption {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }
        public string Content {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }        
        public string TargetName {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }
    }
<UserControl ...
    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking">
    <UserControl.DataContext>
        <ViewModels:MainViewModel />
    </UserControl.DataContext>
    <Grid>
        <dxdo:DockLayoutManager ItemsSource="{Binding Panels}">
            <dxdo:DockLayoutManager.Resources>
                <Style x:Key="BaseStyle"
                       TargetType="dxdo:LayoutPanel">
                    <Setter Property="Caption" Value="{Binding Caption}" />
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <TextBlock Text="{Binding Content}" />
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
                <Style BasedOn="{StaticResource BaseStyle}" TargetType="dxdo:LayoutPanel" />
                <Style BasedOn="{StaticResource BaseStyle}" TargetType="dxdo:DocumentPanel" />
            </dxdo:DockLayoutManager.Resources>
            <dxdo:LayoutGroup x:Name="root">
                <dxdo:LayoutGroup Name="layoutGroup" />
                <dxdo:DocumentGroup Name="documentGroup" />
            </dxdo:LayoutGroup>
        </dxdo:DockLayoutManager>
    </Grid>
</UserControl>

View Example: Bind the View Model Collection with IMVVMDockingProperties

Non-Existing Dock Panels (Advanced)

You can use the LayoutAdapter to place dock panels to existing dock groups, but also to groups that are not created yet. If you choose the latter option, create the target group before you populate it with panels.

Follow the steps below to use the LayoutAdapter in your application:

  1. Create a LayoutAdapter class that implements the ILayoutAdapter interface.
  2. Implement the Resolve method. The method returns the target layout group name for the specified DockLayoutManager and the panel’s ViewModel. When you implement the Resolve method, you can perform any action before a target name is returned.

    public class LayoutAdapter : ILayoutAdapter {
        public string Resolve(DockLayoutManager owner, object item) {
            if (item is PanelViewModel panelViewModel) {
                switch (panelViewModel.State) {
                    case State.Float:
                        FloatGroup floatGroup = new FloatGroup() { Name = $"floatGroup{owner.FloatGroups.Count}" };
                        owner.FloatGroups.Add(floatGroup);
                        return floatGroup.Name;                        
                    case State.Regular:
                        return "documentGroup";
                    default:
                        return panelViewModel.ParentName;
                }
            }
            return String.Empty;
        }
    }
    
  3. Assign a LayoutAdapter instance to the MVVMHelper.LayoutAdapter attached property on the DockLayoutManager to add the LayoutAdapter to DockLayoutManager.

    <UserControl ...
        xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking">
        <UserControl.DataContext>
            <ViewModels:MainViewModel />
        </UserControl.DataContext>
        <Grid>
            <dxdo:DockLayoutManager ItemsSource="{Binding Panels}">
                <dxdo:DockLayoutManager.Resources>
                    <Style x:Key="BaseStyle" TargetType="dxdo:LayoutPanel">
                        <Setter Property="Caption" Value="{Binding Caption}"/>
                        <Setter Property="ContentTemplate">
                            <Setter.Value>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Content}"/>
                                </DataTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                    <Style TargetType="dxdo:LayoutPanel" BasedOn="{StaticResource BaseStyle}" />
                    <Style TargetType="dxdo:DocumentPanel" BasedOn="{StaticResource BaseStyle}" />
                </dxdo:DockLayoutManager.Resources>
                <dxdo:MVVMHelper.LayoutAdapter>
                    <common:LayoutAdapter />
                </dxdo:MVVMHelper.LayoutAdapter>
                <dxdo:LayoutGroup x:Name="root">
                    <dxdo:LayoutGroup Name="layoutGroup"/>
                    <dxdo:DocumentGroup Name="documentGroup" />
                </dxdo:LayoutGroup>
            </dxdo:DockLayoutManager>
        </Grid>
    </UserControl>
    
    public class MainViewModel : ViewModelBase {
        public ObservableCollection<PanelViewModel> Panels {
            get { return GetValue<ObservableCollection<PanelViewModel>>(); }
            set { SetValue(value); }
        }
        public MainViewModel() {
            Panels = new ObservableCollection<PanelViewModel>() {
                new PanelViewModel() { Caption = "One", Content = "A regular panel", State = State.Regular },
                new PanelViewModel() { Caption = "Two", Content = "A regular panel", State = State.Regular },
                new PanelViewModel() { Caption = "Three", Content = "A regular panel", State = State.Regular },
                new PanelViewModel() { Caption = "Four", Content = "A float panel", State = State.Float },
                new PanelViewModel() { Caption = "Five", Content = "A panel", ParentName = "layoutGroup" },
            };
        }
    }
    public enum State { None, Float, Regular }
    public class PanelViewModel : ViewModelBase {
        public string Caption {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }
        public string Content {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }
        public State State {
            get { return GetValue<State>(); }
            set { SetValue(value); }
        }
        public string ParentName {
            get { return GetValue<string>(); }
            set { SetValue(value); }
        }
    }
    

View Example: Bind the View Model Collection with LayoutAdapters

Populate LayoutGroup

This approach allows you to populate a DockLayoutManager.LayoutGroup with a single ViewModel collection.

Dock Panels in Layout Group

Pass the ViewModel collection to the LayoutGroup.ItemsSource property to display dock panels in the LayoutGroup.

<UserControl ...
    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Grid>
        <dxdo:DockLayoutManager Name="dockLayoutManager1">
            <dxdo:DockLayoutManager.LayoutRoot>
                <dxdo:LayoutGroup x:Name="root" Caption="LayoutRoot">
                    <dxdo:DocumentGroup x:Name="documentGroup" ItemsSource="{Binding DocumentViews}" />
                    <dxdo:LayoutGroup
                        x:Name="layoutGroup"
                        ItemsSource="{Binding LayoutViews}"
                        Orientation="Vertical" />
                </dxdo:LayoutGroup>
            </dxdo:DockLayoutManager.LayoutRoot>
        </dxdo:DockLayoutManager>
    </Grid>
</UserControl>
public class MainViewModel : ViewModelBase {
    public ObservableCollection<PanelViewModel> DocumentViews {
        get { return GetValue<ObservableCollection<PanelViewModel>>(); }
        set { SetValue(value); }
    }
    public ObservableCollection<PanelViewModel> LayoutViews {
        get { return GetValue<ObservableCollection<PanelViewModel>>(); }
        set { SetValue(value); }
    }

    public MainViewModel() {
        DocumentViews = new ObservableCollection<PanelViewModel>() { 
            new DocumentViewModel() { Caption = "Document1", Content = "A document panel", Glyph="/Images/change.png"  },
            new DocumentViewModel() { Caption = "Document2", Content = "A document panel", Glyph="/Images/create.png" },
            new DocumentViewModel() { Caption = "Document3", Content = "A document panel", Glyph="/Images/new-16x16.png" },
            new DocumentViewModel() { Caption = "Document4", Content = "A document panel" },
        };
        LayoutViews = new ObservableCollection<PanelViewModel>() {
            new PanelViewModel() { Caption = "One", Content = "A regular panel" },
            new PanelViewModel() { Caption = "Two", Content = "A regular panel" },
            new PanelViewModel() { Caption = "Three", Content = "A regular panel" },
            new PanelViewModel() { Caption = "Five", Content = "A regular panel" },
        };
    }
}

public class PanelViewModel : ViewModelBase
{
    public string Caption {
        get { return GetValue<string>(); }
        set { SetValue(value); }
    }
    public string Content {
        get { return GetValue<string>(); }
        set { SetValue(value); }
    }
}
public class DocumentViewModel : PanelViewModel
{
    public string Glyph {
        get { return GetValue<string>(); }
        set { SetValue(value); }
    }
}

View Example: Populate a DockLayoutManager LayoutGroup with the ViewModels collection

Tabbed Panels in Document Group

The TabbedDocumentUIService is an IDocumentManagerService implementation. Use the TabbedDocumentUIService to show documents as tabbed DocumentPanels inside a DockLayoutManager‘s DocumentGroup.

TabbedDocumentUIService

Refer to the following topic for more information: TabbedDocumentUIService.

View Example: Use Services to implement the IDocumentManagerService Interface

Layout Panels in Layout Group

The DockingDocumentUIService is an IDocumentManagerService implementation. Use the DockingDocumentUIService to show documents as a DockLayoutManager‘s LayoutPanels added to a LayoutGroup.

DockingDocumentUIService

Refer to the following topic for more information: DockingDocumentUIService.

View Example: Use Services to implement the IDocumentManagerService Interface

Customize Appearance

Item Template

You can use the following properties to specify how DockLayoutManager renders collection elements:

Collection Collection Template’s Property Template Selector
DockLayoutManager.ItemsSource DockLayoutManager.ItemTemplate DockLayoutManager.ItemTemplateSelector
LayoutGroup.ItemsSource LayoutGroup.ItemTemplate LayoutGroup.ItemTemplateSelector
<dxdo:DockLayoutManager.ItemTemplate>
    <DataTemplate>
        <dxdo:LayoutPanel Caption="{Binding DisplayName}" />
    </DataTemplate>
</dxdo:DockLayoutManager.ItemTemplate>

Item Styles

You can create Styles that target the DocumentPanel and LayoutPanel classes to bind the ViewModel fields to dock panels.

<dxdo:DockLayoutManager.Resources>
    <Style x:Key="BaseStyle" TargetType="dxdo:LayoutPanel">
        <Setter Property="Caption" Value="{Binding Caption}" />
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <TextBlock Text="{Binding Content}" />
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style BasedOn="{StaticResource BaseStyle}" TargetType="dxdo:LayoutPanel" />
    <Style BasedOn="{StaticResource BaseStyle}" TargetType="dxdo:DocumentPanel" />
</dxdo:DockLayoutManager.Resources>

View Example: Bind the View Model Collection with IMVVMDockingProperties

Data Templates

You can define DataTemplates that target the dock panel ViewModel to initialize panel contents.

<Application.Resources>
    <DataTemplate DataType="{x:Type local:PanelViewModel}">
        <Grid>
            <Button Content="{Binding Glyph}"/>
        </Grid>
    </DataTemplate>
</Application.Resources>
See Also