Taoffi's blog

prisonniers du temps

Inspiring Jargon

I read:

"Arianespace… announced […] that two satellites it had tried to launch to join the European Space Agency's Galileo constellation, had entered a "non-nominal injection orbit"—in other words, gone wrong"  

You probably now know how to better say "I got a bug" (when it is just a "non-nominal behavior")!

Choices in a logical tree view – WPF sample

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:

  • If the node is inclusive: do nothing (just set the selected flag)
  • If the node is exclusive (and selected):
    • Unselect all exclusive siblings (siblings = Parent's Children)
    • Select all inclusive child nodes
    • Select the first exclusive child node if any

 

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)