I need to load a collection of items as documents in AvalonDock 2.0. These objects inherit from an abstract class, for which I want to render a frame inside the document depending on which subclass are.
This is my XAML:
<ad:DockingManager Background="Gray" DocumentsSource="{Binding Path=OpenProjects}"
ActiveContent="{Binding Path=CurrentProject, Mode=TwoWay}">
<ad:DockingManager.DocumentHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=OpenProjects/Name}" />
</DataTemplate>
</ad:DockingManager.DocumentHeaderTemplate>
<ad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type vm:SubclassAViewModel}">
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassBViewModel}">
<Frame Source="Pages/SubclassBProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassCViewModel}">
<Frame Source="Pages/SubclassCProject.xaml" />
</DataTemplate>
</Grid.Resources>
</Grid>
</DataTemplate>
</ad:DockingManager.LayoutItemTemplate>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
So far I've achieved to show as many documents as items are in the OpenProjects
collection, but I can't seem to show anything inside each document.
Plus, I don't know if I'm using ActiveContent
properly: I want to assign to CurrentProject
the ViewModel assigned on the current active document.
Thank you for your time.
The reason why you are not able to see any content is of the way you defined your LayoutItem
templates. This can't work.
Also consider to use a custom control instead of the Frame
. The Frame
is very heavy. Unless you don't need to display HTML, avoid this control. Content navigation is very easy to implement, in case you want to show navigable content. Just wrap your document content into a UserControl
.
You are using the ActiveContent
property properly.
To fix your problem you have three recommended solutions, where the first doesn't exactly meet your requirements. Since you defined the DockingManager.LayoutItemTemplate
wrong, I will show it anyway.
LayoutItemTemplate
In case you only need a single template for all LayoutDocument
and LayoutAnchorable
containers, you can use the DockingManager.LayoutItemTemplate
property. This property accepts a single DataTemplate
. Nested DataTemplate
definitions, like in your code, are generally not supported by WPF.
<ad:DockingManager>
<ad:DockingManager.LayoutItemTemplate>
<DataTemplate>
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
</ad:DockingManager.LayoutItemTemplate>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane />
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
DataTemplate
In more advanced scenarios you display different views based on different models. If the displayed content depends on the data type of the model alone (like in your case), the recommended approach is to provide implicit DataTemplate
definitions.
WPF will automatically apply an implicit DataTemplate
to every data type that matches the DataTemplate.TargetType
of this template.
The DataTemplate
is implicit, if it has no explicit x:Key
value assigned. To ensure that the DataTemplate
can actually be applied automatically, the DataTemplate
must also be defined in the same resource scope as the target type. E.g., defining the DataTemplate
in Application.Resources
of App.xaml, would make the template to be applied automatically in the application scope.
<ad:DockingManager>
<ad:DockingManager.Resources>
<DataTemplate DataType="{x:Type vm:SubclassAViewModel}">
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassBViewModel}">
<Frame Source="Pages/SubclassBProject.xaml" />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SubclassCViewModel}">
<Frame Source="Pages/SubclassCProject.xaml" />
</DataTemplate>
</ad:DockingManager.Resources>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
DataTemplateSelector
The previous solution, which uses implicit DataTemplate
definitions, can be replaced with a DataTemplateSelector
. DataTemplateSelector
is another WPF concept to apply a DataTemplate
selectively.
A DataTemplateSelector
is the recommended choice, if selecting a DataTemplate
may depend on more complex constraints, than the model's data type alone. It allows to e.g. evaluate the data item and chose the appropriate template based on certain criteria.
To define a template selector, you have to extend DataTemplateSelector
, which is expected to return a DataTemplate
.
The easiest way is to define the templates in App.xaml resource dictionary using an x:Key
and then select from them based on the condition.DockingManger
accepts a template selector by assigning the DockingManager.LayoutItemTemplateSelector
property:
App.xaml
Define the explicit DataTemplate
using x:Key
:
<Application.Resources>
<DataTemplate x:Key="SubclassAViewModelTemplate" DataType="{x:Type vm:SubclassAViewModel}">
<Frame Source="Pages/SubclassAProject.xaml" />
</DataTemplate>
<DataTemplate x:Key="SubclassBViewModelTemplate" DataType="{x:Type vm:SubclassBViewModel}">
<Frame Source="Pages/SubclassBProject.xaml" />
</DataTemplate>
<DataTemplate x:Key="SubclassCViewModelTemplate" DataType="{x:Type vm:SubclassCViewModel}">
<Frame Source="Pages/SubclassCProject.xaml" />
</DataTemplate>
</Application.Resources>
DocumentManagerTemplateSelector.cs
The following code uses the Switch Expression, which is available since C# 8.0. It can be replaced with a switch statement or cascaded if-statements.
class DocumentManagerTemplateSelector : DataTemplateSelector
{
#region Overrides of DataTemplateSelector
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return item switch
{
SubclassAViewModel _ => Application.Current.Resources["SubclassAViewModelTemplate"] as DataTemplate,
SubclassBViewModel _ => Application.Current.Resources["SubclassBViewModelTemplate"] as DataTemplate,
SubclassCViewModel _ => Application.Current.Resources["SubclassCViewModelTemplate"] as DataTemplate,
_ => base.SelectTemplate(item, container)
};
}
#endregion
}
MainWindow.xaml
<ad:DockingManager>
<xcad:DockingManager.LayoutItemTemplateSelector>
<local:DocumentManagerTemplateSelector />
</xcad:DockingManager.LayoutItemTemplateSelector>
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With