You should not operate on the item containers but on the items.
To do so, you need to add a IsSelected property to your model. You also need to select the child items of a node.
In order to register a MouseBinding on the TreeViewItem you would have to override the default ControlTemplate of TreeViewItem. The MouseBinding defines a MouseGesture to trigger on "CTRL + LeftClick". A ICommand registered with the MouseBinding executes the actual selection of the item by toggling the IsSelected property.
To get the selected items you simply iterate over the source collection (traverse the tree structure) to collect all items where IsSelected equals true.
TreeItem.cs
public class TreeItem : INotifyPropertyChanged
{
public TreeItem(string value)
{
this.Value = value;
this.Children = new ObservableCollection<TreeItem>();
}
private void SelectAllChildren(IEnumerable<TreeItem> children, bool isSelected)
{
foreach (TreeItem child in children)
{
child.IsSelected = isSelected;
SelectAllChildren(child.Children, isSelected);
}
}
public string Value { get; set; }
public ObservableCollection<TreeItem> Children { get; set; }
// Toggle IsSelected
public ICommand SelectItemCommand => new RelayCommand(param => this.IsSelected ^= true);
private bool isSelected;
public bool IsSelected
{
get => this.isSelected;
set
{
this.isSelected = value;
OnPropertyChanged();
SelectAllChildren(this.Children, this.IsSelected);
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion Implementation of INotifyPropertyChanged
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.TreeItems = new ObservableCollection<TreeItem>();
}
private IEnumerable<TreeItem> GetSelectedItems(IEnumerable<TreeItem> items)
{
List<TreeItem> selectedItems = new List<TreeItem>();
foreach (TreeItem item in items)
{
if (item.IsSelected)
{
selectedItems.Add(item);
}
else // Check if unselected node has selected child nodes
{
var selectedChildItems = GetSelectedItems(item.Children);
selectedItems.AddRange(selectedChildItems);
}
}
return selectedItems;
}
public ObservableCollection<TreeItem> TreeItems { get; set; }
}
MainWindow.xaml
<Window>
<Window.Resources>
<ViewModel />
</Window.Resources>
<TreeView ItemsSource="{Binding TreeItems}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type TreeItem}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Value}" />
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="Expander"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Background="Transparent"
BorderThickness="0"
RenderTransformOrigin="0.5, 0.5">
<ToggleButton.RenderTransform>
<RotateTransform />
</ToggleButton.RenderTransform>
<ToggleButton.Content>
<TextBlock Text=""
FontFamily="Segoe MDL2 Assets" />
</ToggleButton.Content>
</ToggleButton>
<Border x:Name="SelectionBorder">
<ContentPresenter x:Name="PART_Header"
Content="{TemplateBinding Header}">
<!-- Register to handle CTRL+LeftClick gesture -->
<ContentPresenter.InputBindings>
<MouseBinding Command="{Binding SelectItemCommand}">
<MouseBinding.Gesture>
<MouseGesture Modifiers="Control"
MouseAction="LeftClick" />
</MouseBinding.Gesture>
</MouseBinding>
</ContentPresenter.InputBindings>
</ContentPresenter>
</Border>
</StackPanel>
<ItemsPresenter x:Name="ItemsHost"
Visibility="Collapsed"
Margin="12,0,0,0" />
</StackPanel>
<!-- Animate the node's expander -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ExpansionStates">
<VisualState x:Name="Expanded">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Expander"
Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
To="90"
Duration="0:0:0.1" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemsHost"
Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0"
Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter TargetName="Expander"
Property="Visibility"
Value="Collapsed" />
</Trigger>
<!-- Handle item selection visuals -->
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter TargetName="SelectionBorder"
Property="Background"
Value="DodgerBlue" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Window>