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.
Note
Use either the Items or ItemsSource property to specify DockLayoutManager children.
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 Groups
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>
Non-Existing Dock Groups (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:
- Create a LayoutAdapter class that implements the ILayoutAdapter interface.
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; } }
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); } } }
Populate LayoutGroup
This approach allows you to populate a DockLayoutManager.LayoutGroup with a single ViewModel collection.
Note
Use either the Items or ItemsSource property to specify LayoutGroup children.
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); }
}
}
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.
Refer to the following topic for more information: TabbedDocumentUIService.
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.
Refer to the following topic for more information: DockingDocumentUIService.
Configure Generated Panels
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>
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>