Notes on Software

  • Other Links

Checkboxes in WPF TreeView

Posted by K. M. on August 3, 2008

Josh Smith recently posted an interesting article on CodeProject about using CheckBoxes in a WPF TreeView.

“The fundamental problem is that as you navigate the tree via arrow keys, a TreeViewItem will first take input focus, and then the CheckBox it contains will take focus upon the next keystroke. Both the TreeViewItem and CheckBox controls are focusable. The result is that you must press an arrow key twice to navigate from item to item in the tree.”

His solution is to set Focusable to false on the CheckBox and make the TreeViewItem a “virtual ToggleButton” by applying attached properties.

Since the problem has to do with focus, it can also be solved by setting focus on the CheckBox when the TreeViewItem receives focus. The class shown below defines an attached property – FocusedChildName – that sets focus on a named element in the visual tree of the target element. Adding a Setter to the Style for the TreeViewItem

<Setter Property=”local:FocusHelper.FocusedChildName” Value=”checkbox”/>

and setting up a binding on the Focusable property of the CheckBox

<CheckBox x:Name=”checkbox”
    Focusable=”{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=IsSelected}”

produces the desired behavior

public static class FocusHelper
{
    
public static string GetFocusedChildName(DependencyObject obj)
    {
        
return (string)obj.GetValue(FocusedChildNameProperty);
    }

    public static void SetFocusedChildName(DependencyObject obj, string value)
    {
        obj.SetValue(FocusedChildNameProperty, value);
    }
    
public static readonly DependencyProperty FocusedChildNameProperty =
        
DependencyProperty.RegisterAttached(“FocusedChildName”, typeof(string), typeof(FocusHelper),
            
new UIPropertyMetadata(FocusedChildName_PropertyChanged));

    private static void FocusedChildName_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        
UIElement element = d as UIElement;
        
if (element != null && !string.IsNullOrEmpty((string)e.NewValue))
            element.GotKeyboardFocus += element_GotKeyboardFocus;
        
if (element != null && string.IsNullOrEmpty((string)e.NewValue))
            element.GotKeyboardFocus -= element_GotKeyboardFocus;
    }

    private static void element_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
    {
        
if (e.OriginalSource == sender)
        {
            (sender
as DispatcherObject).Dispatcher.BeginInvoke(DispatcherPriority.Input,
                
new Action(() =>
                {
                    
DependencyObject obj = sender as DependencyObject;
                    
string name = (string)obj.GetValue(FocusHelper.FocusedChildNameProperty);
                    
IInputElement element = FindNamedElementInVisualTree(obj, name) as IInputElement;
                    
if (element != null)
                        element.Focus();
                }));
        }
    }

    private static DependencyObject FindNamedElementInVisualTree(DependencyObject parent, string name)
    {
        
int count = VisualTreeHelper.GetChildrenCount(parent);
        
for (int i = 0; i < count; i++)
        {
            
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
            
if ((string)child.GetValue(FrameworkElement.NameProperty) == name) return child;
            child = FindNamedElementInVisualTree(child, name);
            
if (child != null) return child;
        }
        
return null;
    }
}

Leave a comment