I have an observable collection of nested objects which I am displaying in a TreeView
in WPF.
public class NestedContainer
{
public FXR1.FXContainer myContainer { get; set; }
public string XID { get { return myContainer.XID; } }
public FXR1.FXActionData ActionData { get { return myContainer.ActionData; } }
public FXR1.FXModifier Modifier { get { return myContainer.Modifier; } }
public List<NestedContainer> childContainers { get; set; } = new List<NestedContainer>();
}
I was successful in creating the nested structure that I want, using the following XAML code it displays in a TreeView like so:
<TreeView x:Name="FXNodeTree" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:NestedContainer}" ItemsSource="{Binding Path=childContainers}">
<TextBlock Text="{Binding Path=XID}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
However, I am also trying to display the ActionData
and Modifier
(if any) associated with each Container, as items in the TreeView
located just below the Container's childContainers
. These items do not have child containers of their own, so they are just additional pieces of info belonging to each Container. My initial thought after some research was to use Multibinding and bind ActionData
and Modifier
in addition to childContainers
, but I was unable to find any similar examples of this so I am unsure if this is the ideal solution. My knowledge of WPF is very entry level, so I apologize if this is a very basic problem or if I'm missing an obvious solution. Any help is very much appreciated.
If I understood you correctly, you want to display additional information of each node data.
For this purpose, you should override the default ControlTeplate
of the TreeViewItem
. This way you can inject a second content host below the items host (ItemsPresenter
).
If you introduce an attached property to hold a DataTemplate
that describes the additional content you can avoid to subclass TreeViewItem
. This attached property can be defined on any suitable type. This example chooses the MainWindow
.
To avoid memory leaks, the binding source must always implement INotifyPropertyChanged
or preferably implement properties as dependency properties. Even if the property values won't change.
The following example shows a fragment of the extracted TreeViewItem
default Style
. It misses other referenced resources in order to compact the example.
You can extract the complete Style using the XAML designer and then paste the Style
for the TreeViewItem
from below to replace the extracted Style
of the TreeViewItem
.
I have annotated the modified parts to help spotting the required modifications.
MainWindow.xanl.cs
partial class MainWindow : Window
{
public static DataTemplate GetExtendedContentItemTemplate(DependencyObject attachingElement)
=> (DataTemplate)attachingElement.GetValue(ExtendedContentItemTemplateProperty);
public static void SetExtendedContentItemTemplate(DependencyObject attachingElement, DataTemplate value)
=> attachingElement.SetValue(ExtendedContentItemTemplateProperty, value);
public static readonly DependencyProperty ExtendedContentItemTemplateProperty = DependencyProperty.RegisterAttached(
"ExtendedContentItemTemplate",
typeof(DataTemplate),
typeof(MainWindow),
new PropertyMetadata(default));
}
MainWindow.xaml
<Window>
<Window.Resources>
<!-- The example DataTemplate for the extended content -->
<DataTemplate x:Key="ExtendedDataContentTemplate"
DataType="NestedContainer">
<StackPanel>
<TextBlock Text="{Binding ActionData}" />
<TextBlock Text="{Binding Modifier}" />
</StackPanel>
</DataTemplate>
<!--
The fragment of the complete TreeViewItem style.
The first setter that sets the attached 'ExtendedContentItemTemplate' property
and the ControlTemplate that extends the layout to add the ContentControl
for the additional content are of special interest here.
-->
<Style TargetType="{x:Type TreeViewItem}">
<!-- Assign the DataTemplate for the extended content to the attached property -->
<Setter Property="local:MainWindow.ExtendedContentItemTemplate"
Value="{StaticResource ExtendedDataContentTemplate}" />
<Setter Property="Background"
Value="Transparent" />
<Setter Property="HorizontalContentAlignment"
Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="VerticalContentAlignment"
Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" />
<Setter Property="Padding"
Value="1,0,0,0" />
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="FocusVisualStyle"
Value="{StaticResource TreeViewItemFocusVisual}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19"
Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <!-- Default header row -->
<RowDefinition /> <!-- Default child items row -->
<RowDefinition Height="Auto" /> <!-- New extra content row -->
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander"
ClickMode="Press"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Style="{StaticResource ExpandCollapseToggleStyle}" />
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Grid.Column="1"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.Column="1"
Grid.ColumnSpan="2"
Grid.Row="1" />
<!--
The host for the extra content below the child items.
For this purpose we have to introduce a third row to the Grid.
The Content is the data item itself (in this case 'NestedContainer').
The ContentTemplate binds to the attached property ExtendedContentItemTemplate
that is set on the TreeViewItem via the current Style (see style setter above).
-->
<ContentControl Grid.Row="2"
Grid.Column="1"
Content="{TemplateBinding DataContext}"
ContentTemplate="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MainWindow.ExtendedContentItemTemplate)}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded"
Value="false">
<Setter Property="Visibility"
TargetName="ItemsHost"
Value="Collapsed" />
</Trigger>
<Trigger Property="HasItems"
Value="false">
<Setter Property="Visibility"
TargetName="Expander"
Value="Hidden" />
</Trigger>
<Trigger Property="IsSelected"
Value="true">
<Setter Property="Background"
TargetName="Bd"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected"
Value="true" />
<Condition Property="IsSelectionActive"
Value="false" />
</MultiTrigger.Conditions>
<Setter Property="Background"
TargetName="Bd"
Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}" />
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}" />
</MultiTrigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingPanel.IsVirtualizing"
Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="NestedContainer"
ItemsSource="{Binding ChildContainers}">
<TextBlock Text="{Binding XID}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Window>