✅ WPF trees with some items that can't be selected

I'm building a UI with a tree view where some items exist only to group children. The tree will function as a kind of vertical tree control. When most items in the tree are selected, I'll show a control next to the tree. But the items that are only groups don't have a control to show. So I don't want to allow their selection. How do I pull that off?
47 Replies
Mayor McCheese
Mayor McCheese9mo ago
I've done this before, but it was years ago, let me see if I can find th repo
JakenVeina
JakenVeina9mo ago
framework?
Will Pittenger
Will Pittenger9mo ago
Well, I'm using .NET 7 if that's what you mean.
Mayor McCheese
Mayor McCheese9mo ago
Oh I sorta assumed wpf
Will Pittenger
Will Pittenger9mo ago
Well, the topic does say WPF. I thought you meant which version of .NET. That's implied when you talk about WPF.
Mayor McCheese
Mayor McCheese9mo ago
So this is really dirty
Will Pittenger
Will Pittenger9mo ago
Dirty????
Mayor McCheese
Mayor McCheese9mo ago
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">

<Window.Resources>
<HierarchicalDataTemplate x:Key="CheckedDataTemplate">
<CheckBox VerticalContentAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=IsChecked}">
<CheckBox.Content>
<Label VerticalAlignment="Center" Content="{Binding Path=Name}"></Label>
</CheckBox.Content>
</CheckBox>
</HierarchicalDataTemplate>

<HierarchicalDataTemplate x:Key="DisplayDataTemplate">
<Label VerticalAlignment="Center" Content="{Binding Path=Name}"></Label>
</HierarchicalDataTemplate>

<local:TreeViewItemTemplateSelector x:Key="ItemTemplateSelector"></local:TreeViewItemTemplateSelector>
</Window.Resources>
<DockPanel>
<TreeView x:Name="TreeView" MinWidth="100" ItemTemplateSelector="{StaticResource ItemTemplateSelector}"></TreeView>

<StackPanel DockPanel.Dock="Right">
<Label Content="Name"></Label>
<TextBox x:Name="Name"></TextBox>
<CheckBox x:Name="CheckBox" Content="Is Checkable"></CheckBox>
<Button Content="Add Node" Click="AddNode_OnClick"></Button>
</StackPanel>
</DockPanel>
</Window>
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">

<Window.Resources>
<HierarchicalDataTemplate x:Key="CheckedDataTemplate">
<CheckBox VerticalContentAlignment="Center" VerticalAlignment="Center" IsChecked="{Binding Path=IsChecked}">
<CheckBox.Content>
<Label VerticalAlignment="Center" Content="{Binding Path=Name}"></Label>
</CheckBox.Content>
</CheckBox>
</HierarchicalDataTemplate>

<HierarchicalDataTemplate x:Key="DisplayDataTemplate">
<Label VerticalAlignment="Center" Content="{Binding Path=Name}"></Label>
</HierarchicalDataTemplate>

<local:TreeViewItemTemplateSelector x:Key="ItemTemplateSelector"></local:TreeViewItemTemplateSelector>
</Window.Resources>
<DockPanel>
<TreeView x:Name="TreeView" MinWidth="100" ItemTemplateSelector="{StaticResource ItemTemplateSelector}"></TreeView>

<StackPanel DockPanel.Dock="Right">
<Label Content="Name"></Label>
<TextBox x:Name="Name"></TextBox>
<CheckBox x:Name="CheckBox" Content="Is Checkable"></CheckBox>
<Button Content="Add Node" Click="AddNode_OnClick"></Button>
</StackPanel>
</DockPanel>
</Window>
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public TreeNode DataSource { get; }

public MainWindow()
{
InitializeComponent();

DataSource = new TreeNode("Root", new ObservableCollection<TreeNode>());

TreeView.ItemsSource = DataSource.Children;
}


public void AddNode(TreeNode? parent, TreeNode child)
{
(parent ??= DataSource).Children.Add(child);
}

private void AddNode_OnClick(object sender, RoutedEventArgs e)
{
var parent = TreeView.SelectedItem as TreeNode;

var child = new TreeNode(Name.Text, new ObservableCollection<TreeNode>(), CheckBox.IsChecked ?? false);

AddNode(parent, child);
}
}

public class TreeNode
{
public string Name { get; }
public ICollection<TreeNode> Children { get; }

public bool HasChildren => Children.Any();
public bool HasCheckBox { get; set; }

public TreeNode(string name, ICollection<TreeNode>? children, bool hasCheckBox = false)
{
Name = name;
Children = children ?? new List<TreeNode>();
HasCheckBox = hasCheckBox;
}
}

public class TreeViewItemTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Window window = Application.Current.MainWindow;

if (item is TreeNode node && node.HasCheckBox)
{
return window.Resources["CheckedDataTemplate"] as DataTemplate;
}

return window.Resources["DisplayDataTemplate"] as DataTemplate;

}
}
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public TreeNode DataSource { get; }

public MainWindow()
{
InitializeComponent();

DataSource = new TreeNode("Root", new ObservableCollection<TreeNode>());

TreeView.ItemsSource = DataSource.Children;
}


public void AddNode(TreeNode? parent, TreeNode child)
{
(parent ??= DataSource).Children.Add(child);
}

private void AddNode_OnClick(object sender, RoutedEventArgs e)
{
var parent = TreeView.SelectedItem as TreeNode;

var child = new TreeNode(Name.Text, new ObservableCollection<TreeNode>(), CheckBox.IsChecked ?? false);

AddNode(parent, child);
}
}

public class TreeNode
{
public string Name { get; }
public ICollection<TreeNode> Children { get; }

public bool HasChildren => Children.Any();
public bool HasCheckBox { get; set; }

public TreeNode(string name, ICollection<TreeNode>? children, bool hasCheckBox = false)
{
Name = name;
Children = children ?? new List<TreeNode>();
HasCheckBox = hasCheckBox;
}
}

public class TreeViewItemTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Window window = Application.Current.MainWindow;

if (item is TreeNode node && node.HasCheckBox)
{
return window.Resources["CheckedDataTemplate"] as DataTemplate;
}

return window.Resources["DisplayDataTemplate"] as DataTemplate;

}
}
maybe rough is a better term no mvvm, pretty much wpf as winforms but the real concept is the hierarcal data template and the data template selector if I understand the problem your asking anyway
Will Pittenger
Will Pittenger9mo ago
Until your sample arrived, I thought you meant what I wanted was dirty.
Mayor McCheese
Mayor McCheese9mo ago
no, I mean my code is ugly now that I'm re-reading you may be asking for something slightly different
Will Pittenger
Will Pittenger9mo ago
Uh, what did you think I wanted?
Mayor McCheese
Mayor McCheese9mo ago
displaying diffrent classes of nodes according to types ( or some other discriminator )
Will Pittenger
Will Pittenger9mo ago
Nope. There are some nodes that the user can't select. In Visual Studio's preferences window, there are tree elements that select the first child. In my case, it's similar, but I'd prefer to ignore the user's attempts to select some elements.
Mayor McCheese
Mayor McCheese9mo ago
oh, so if they select a node, based on some sieve you'd essentially ignore it? they can't select it at all?
Will Pittenger
Will Pittenger9mo ago
Correct, or the tree ignores the click.
Mayor McCheese
Mayor McCheese9mo ago
hmmmm probably the easiest is to bind a command to the node and ignore the command if it doesn't fit the criteria I've not done wpf for a long time though
Will Pittenger
Will Pittenger9mo ago
You use something newer?
Mayor McCheese
Mayor McCheese9mo ago
nah, it was always a hobby framework for me and I got less interested in wpf Using the selected template you can get creative around which items are interactable so for instrance, I have an unused property called "HasChildren" which could be part of your template selector so for a container template it's display only and for other template types it's more interactive so the concept is similar,
Will Pittenger
Will Pittenger9mo ago
Hmm. I was going with one template as all entries in the source are wrapped in what I'm calling ViewTreeData instances. Though calling that property CanBeSelected is more appropriate. ViewTreeData has that now. My lone DataTemplate generates a specialized ViewTreeItem.
Mayor McCheese
Mayor McCheese9mo ago
I used to play something called legend of the five rings, which is a d10 pen and paper d&d game, but has pretty complex die rolling scenarios, so I grouped "scenarios" by containers in a tree And using the selector I could pick the appropriate "viewer" I think for containers my view was just editing the container name
Will Pittenger
Will Pittenger9mo ago
@Mayor McCheese Basically, I wasn't planning on the tree items being much different other than if you can select them. Each would be a icon with text. I might end up causing the text color and/or style to change, but the icon for entries you can't select will always be special.
Mayor McCheese
Mayor McCheese9mo ago
Are you going the mvvm route?
Will Pittenger
Will Pittenger9mo ago
Sort of. The ViewTreeData is serving as the View Model.
Mayor McCheese
Mayor McCheese9mo ago
Can you show your xaml?
Will Pittenger
Will Pittenger9mo ago
Not at the moment. Wrong computer.
Mayor McCheese
Mayor McCheese9mo ago
Np I'll take a look again a bit later today I'm on crisis schedule today
JakenVeina
JakenVeina9mo ago
my bad, I someow missed "WPF" in the title so, like you just want certain tree nodes to not be selectable?
Will Pittenger
Will Pittenger9mo ago
Correct.
JakenVeina
JakenVeina9mo ago
what the trouble you're having with that?
Will Pittenger
Will Pittenger9mo ago
Huh? Having trouble? I don't know how to prevent selection.
JakenVeina
JakenVeina9mo ago
is there not a styling property you can set?
Will Pittenger
Will Pittenger9mo ago
You mean a property in the tree? No. Ditto for a TreeItem.
JakenVeina
JakenVeina9mo ago
Focusable="false"?
Will Pittenger
Will Pittenger9mo ago
<TreeView
x:Name="treeLogicalSelector">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="TreeData:VisualTreeData"
ItemsSource="{Binding Children}">
<StackPanel
Orientation="Horizontal"
ToolTip="{Binding LocalizedLongDesc}">
<Emoji:TextBlock
Text="{Binding Icon}"
VerticalAlignment="Center" />
<Label
Content="{Binding LocalizedName}"
VerticalAlignment="Center" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<TreeView
x:Name="treeLogicalSelector">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
DataType="TreeData:VisualTreeData"
ItemsSource="{Binding Children}">
<StackPanel
Orientation="Horizontal"
ToolTip="{Binding LocalizedLongDesc}">
<Emoji:TextBlock
Text="{Binding Icon}"
VerticalAlignment="Center" />
<Label
Content="{Binding LocalizedName}"
VerticalAlignment="Center" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Mayor McCheese
Mayor McCheese9mo ago
I'm at the gym; but remember when I said dirty? Naming controls is usually not needed in wpf It's kinda frowned on mostly But at a glance I like @V.EINA Jaken's focusable; but I'd use a template selector too to pick the data template; I don't like really long templates.
JakenVeina
JakenVeina9mo ago
definitely
Will Pittenger
Will Pittenger9mo ago
Why do I need more templates?
JakenVeina
JakenVeina9mo ago
because you have different types of items that you want to display differently you'll setup a HierarchalDataTemplate for the group items that have children, and you want to be not-selectable and you'll have a regular DataTemplate for the rest
Will Pittenger
Will Pittenger9mo ago
No. I don't. All items are displayed the same. Some are selectable. Some aren't. Some items with children are selectable.
JakenVeina
JakenVeina9mo ago
what differentiates items from being selectable or not?
Will Pittenger
Will Pittenger9mo ago
They exist for grouping ONLY. The tree functions a little like a tab control. Only some items in the tree don't correspond to anything to be displayed next to the tree.
JakenVeina
JakenVeina9mo ago
yes, what differentiates those items?
Will Pittenger
Will Pittenger9mo ago
I declared a CanBeSelected property. If it's true, the item is a group, but no more. There's nothing to display.
JakenVeina
JakenVeina9mo ago
bind that to Focusable
Mayor McCheese
Mayor McCheese9mo ago
For one, extending logic into xaml is a bad practice, it's difficult to debug and separates out concerns in unexpected ways But if it's always the one deal then sure just bind the property
Accord
Accord9mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Will Pittenger
Will Pittenger9mo ago
Thanks anyway. But after a few days thinking about it, the groups that can be selected no longer made sense. So I removed them.