Using check boxes in TreeView control is a handy way for presenting choices in their logical tree-like structure.
In real life, though, choices can be a mix of inclusive (check-box) and exclusive (radio-button) options.
I expose here a solution for using that mix of option types in one same tree view.
The problem
The problem is divided into three main subjects:
- How to use a mix of checkbox / radio button nodes in the same tree view control
- How to get a radio button to be toggled from checked to unchecked status: Checkboxes are, 'naturally', able to be toggled from checked to unchecked status. This is not the case for Radio buttons. The result is that when you use radio button in a tree view, you will be able to check it but not to get it uncheck!
- How to handle exclusive choices selection. That is when an exclusive option gets selected (checked), for instance,we must unselect all other exclusive sibling options.
To solve the first question, we will use:
- A tree node object which indicates its option type (exclusive / inclusive)
- Hierarchical control templates for each choice type
- A template selector which will select the correct template according to the node object choice type
To solve the second, we will simply create a new Toggled Radio Button (which derives from RadioButton) and get this new object handle the Click event to toggle its selection status.
public class RadioToggleButton : RadioButton
{
protected override void OnClick()
{
IsChecked = !IsChecked;
}
}
The third question will be solved by implementing the required behaviors within our special tree node object.
The TreeNode object
The TreeNode object exposes few properties:
- A Title
- A Parent node (TreeNode)
- A list of Children (List of TreeNode items)
- A boolean flag which indicates if the node represents an exclusive choice option
- A boolean flag which indicates if the node is selected
Through these properties, TreeNode object can expose other properties like its Root node, the First exclusive parent or descendant… etc.
The TreeNode Hierarchical data template
<UserControl.Resources>
…
…
<!-- hierarchical template for checkbox treeview items -->
<HierarchicalDataTemplate x:Key="checkBoxTemplate"
DataType="{x:Type app:TreeNode}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False"
VerticalAlignment="Center"
IsChecked="{Binding IsSelected, Mode=TwoWay}" />
<TextBlock Text="{Binding Title}" />
</StackPanel>
</HierarchicalDataTemplate>
<!-- hierarchical template for (toggled) radio buttons treeview items -->
<HierarchicalDataTemplate x:Key="radioButtonTemplate"
DataType="{x:Type app:TreeNode}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<ctrl:RadioToggleButton Focusable="False"
VerticalAlignment="Center"
IsChecked="{Binding IsSelected, Mode=TwoWay}" />
<TextBlock Text="{Binding Title}" Margin="4, 1, 0, 0" />
</StackPanel>
</HierarchicalDataTemplate>
</UserControl.Resources>
The TreeView node's Item template selector
public class TreeNodeXTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(
object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
TreeNode node = item as TreeNode;
if (element != null && node != null)
{
if (node.IsExclusive)
return element.FindResource("radioButtonTemplate")
as HierarchicalDataTemplate;
return element.FindResource("checkBoxTemplate")
as HierarchicalDataTemplate;
}
return null;
}
}
We can now use an ItemTemplateSelector to tell the Tree view control to select the adequate data template for each item according the tree node choice selection type (exclusive / inclusive)
<UserControl.Resources>
<app:TreeNodeXTemplateSelector x:Key="templateSelector" />
…
…
</UserControl.Resources>
<TreeView x:Name="treeview1" ItemsSource="{Binding Root.Children}"
ItemTemplateSelector="{StaticResource templateSelector}"/>
Exclusive node selection behavior
TreeNode selection behavior can be summarized as follows:
public void UpdateSelection()
{
if(! _isExclusive)
return;
if(_isSelected == true)
{
UnSelectSiblings();
}
SelectChildren(_isSelected);
}
protected void SelectChildren(bool selected)
{
if(! selected)
{
UnSelectChildren();
return;
}
TreeNode firstEx = FirstExclusiveChild;
if(firstEx != null)
firstEx.IsSelected = selected;
foreach(TreeNode node in _children)
{
if(node.IsExclusive)
continue;
node.SetSelection(value: selected, updateChildren: true);
}
}
Sample screenshot
Download the sample code TreeViewRadioAndCheckButtons.zip (67.88 kb)