The following solution leaves the default Tab navigation untouched, since you want custom behavior only for pressing F6 specifically. For more information on a similar tab navigation solution, refer to the last paragraph.
If you add an event handler for PreviewKeyDown
or any other key event on a particular control it will only be fired if the corresponding control has keyboard focus. This is why you should register the handler on the the containing view, user control or parent window (important for cases where there is no focused control or the focused control is not one of those that we keep track of or add the handler to).
<Window ...
PreviewKeyDown="CycleFocus">
Assign a name to each control in the window that you want to focus.
<StackPanel>
<Menu x:Name="MyMenu">
<MenuItem Header="Menu Item">
<MenuItem Header="Sub Menu Item"/>
</MenuItem>
</Menu>
<ComboBox x:Name="MyComboBox" ItemsSource="ABC"/>
<TabControl x:Name="MyTabControl">
<TabItem Header="Tab"/>
</TabControl>
</StackPanel>
Create an array of these controls in the order that you want to cycle through them. Please note that this is just an example to get the idea. You could also store only the names of the controls and resolve them later or search the visual tree or anything else. There are multiple options depending on your requirements.
public partial class MainWindow
{
private IInputElement[] FocusChain;
public MainWindow()
{
InitializeComponent();
FocusChain = new IInputElement[]
{
MyMenu,
MyComboBox,
MyTabControl
};
}
// ...other code.
}
Create the event the event handler for detecting the key and cycling focus.
- First, check if the right key was pressed, otherwise do nothing.
- Use the first focus chain element, if none of the known controls is focused.
- Check for each focusable item if the keyboard focus is within. Note that for e.g. a
ComboBox
the focused item could be a ComboBoxItem
, not the ComboBox
itself, but we want to treat this case the same way.
- If the focus is within a known element, get the next focusable element. The modulus (
%
) ensures that the first element is used, if the last is currently focused (otherwise the index would be out of range).
- Finally, set the logical and keyboard focus to the next focusable element.
private void Menu_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.F6)
return;
var next = FocusChain[0];
for (var i = 0; i < FocusChain.Length; i++)
{
if (FocusChain[i].IsKeyboardFocusWithin)
{
next = FocusChain[(i + 1) % FocusChain.Length];
break;
}
}
next.Focus();
Keyboard.Focus(next);
}
If you want to achieve something similar using Tab navigation, you could do it like this.
private void Menu_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key != Key.F6)
return;
if (Keyboard.FocusedElement is UIElement uiElement)
uiElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
In this case Tab and F6 would work the same. Please note that in case of Menu
when tabbing through, all MenuItem
s will receive keyboard focus one after another. In order to enable focus only once for the whole Menu
, set the TabNavigation
attached property to Once
.
<Menu KeyboardNavigation.TabNavigation="Once">
For more information on logical and keyboard focus, you can refer to the documentation.