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
and setting up a binding on the Focusable property of the CheckBox
Focusable=”{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=IsSelected}”
produces the desired behavior
{
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;
}
}