I'm seeing strange behavior when it comes to focus and keyboard navigation. In the example below I have a simple ItemsControl that has been templated so that it presents a list of CheckBoxes bound to the ItemsSource.
<ItemsControl FocusManager.IsFocusScope="True"
ItemsSource="{Binding ElementName=TheWindow, Path=ListOStrings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
For some strange reason the FocusManager.IsFocusScope="True" assignment causes keyboard focus to fail to be set when checking a checkbox via a mouse click and for focus to jump out of the ItemsControl when a check box is checked using the space bar on the keyboard. Both symptoms seem to point to some strange navigation happening when the checkbox is checked but I'm having a hard time getting to the bottom of it.
This problem occurs if I set any parent element up the visual tree as a focus scope using this method. If I remove the FocusManager.IsFocusScope="True" then the problems go away. Unfortunately I'm seeing this problem in a larger project where I cannot just remove these focus scopes without worrying about other focus related consequences.
Could somebody explain to me the strange behavior I'm seeing? Is this a bug or am I just completely missing something?
This article explains it very well: http://www.codeproject.com/KB/WPF/EnhancedFocusScope.aspx
For What was FocusScope Designed?
Microsoft uses FocusScope in WPF to
create a temporary secondary focus.
Every ToolBar and Menu in WPF has its
own focus scope.
With this knowledge, we can clearly
see why we have those problems:
A toolbar button should not execute
commands on itself, but on whatever
had focus before the toolbar was
clicked. To accomplish this, routed
commands ignore the focus from focus
scopes and use the 'main' logical
focus instead. This explains why
routed commands don't work inside
focus scopes.
Why does the large text box in the
test application screenshot still
display a caret? I don't know the
answer to this - but why shouldn't it?
Granted, the text box doesn't have the
keyboard focus (the small text box in
the WPF focus scope has that); but it
still has the main logical focus in
the active Window and is the receiver
of all routed commands.
And this part covers the behavior you're seeing
Why does the keyboard focus move to
the large text box when you tab to the
CheckBox in the WPF focus scope and
press Space to toggle it?
Well, this is exactly what you expect
when you click a menu item or a
toolbar: the keyboard focus should
return to the main focus. All
ButtonBase-derived controls will do
this.
@Meleak explained the problem very well. Please read the article http://www.codeproject.com/KB/WPF/EnhancedFocusScope.aspx to fully understand what the problem is and how to solve it. I will just add the complete implementation of IsEnhancedFocusScope
attached behavior mentioned in the article:
public static class FocusExtensions
{
private static bool SettingKeyboardFocus { get; set; }
public static bool GetIsEnhancedFocusScope(DependencyObject element) {
return (bool)element.GetValue(IsEnhancedFocusScopeProperty);
}
public static void SetIsEnhancedFocusScope(DependencyObject element, bool value) {
element.SetValue(IsEnhancedFocusScopeProperty, value);
}
public static readonly DependencyProperty IsEnhancedFocusScopeProperty =
DependencyProperty.RegisterAttached(
"IsEnhancedFocusScope",
typeof(bool),
typeof(FocusExtensions),
new UIPropertyMetadata(false, OnIsEnhancedFocusScopeChanged));
private static void OnIsEnhancedFocusScopeChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) {
var item = depObj as UIElement;
if (item == null)
return;
if ((bool)e.NewValue) {
FocusManager.SetIsFocusScope(item, true);
item.GotKeyboardFocus += OnGotKeyboardFocus;
}
else {
FocusManager.SetIsFocusScope(item, false);
item.GotKeyboardFocus -= OnGotKeyboardFocus;
}
}
private static void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) {
if (SettingKeyboardFocus) {
return;
}
var focusedElement = e.NewFocus as Visual;
for (var d = focusedElement; d != null; d = VisualTreeHelper.GetParent(d) as Visual) {
if (FocusManager.GetIsFocusScope(d)) {
SettingKeyboardFocus = true;
try {
d.SetValue(FocusManager.FocusedElementProperty, focusedElement);
}
finally {
SettingKeyboardFocus = false;
}
if (!(bool)d.GetValue(IsEnhancedFocusScopeProperty)) {
break;
}
}
}
}
}
In your XAML you just need to set this attached property instead of standard IsFocusScope
property:
<ItemsControl my:FocusExtensions.IsEnhancedFocusScope="True"
ItemsSource="{Binding ElementName=TheWindow, Path=ListOStrings}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It will work as you expect the focus scope to work.