13
votes

We have a WPF .NET 4.0 C# based application. We built our user interface from XML definitions (not XAML) but underneath we use a WPF to present the UI. That is at runtime, we create the WPF UI based on our XML definition.

We have a problem with tab navigation. We set TabStop, TabIndex, for text and combo box controls.
But tab navigation is not working. How to make tab navigation work for this layout?

enter image description here

4
Have you looked at the KeyboardNavigation class?rfmodulator
How are you setting the TabIndex properties for your controls in the code-behind for this interface? You need to be more clear about what is not working. Post some code.Bernard
What do you mean by 'not working'? Is it 'you hit Tab but the focus won't move'? Or it moves, but moves wrong? In which way is it wrong?Pavel Gatilov
"But tab navigation is not working" - What do you mean exactly? It jumps at random or not at all? BTW, if you don't set any TabIndex, it will jump in the order you add the children.Bogdan Olteanu

4 Answers

37
votes

WPF treats the entire UI Tree as a single Tab scope. It isn't broken up into smaller areas such as you would expect. This includes controls inside UserControls.

For example, if you had

<StackPanel>
    <TextBox Name="TextBox1" />
    <MyUserControl />
    <TextBox Name="TextBox3" />
</StackPanel>

And MyUserControl looked like

<MyUserControl>
    <TextBox Name="TextBox2"  />
</MyUserControl>

The default tab cycle would be TextBox1, TextBox2, TextBox3. This is because no TabIndex properties are defined, so all controls run at the default tab order, which is the order in which they're added to the UI.

If you set the TabIndex on your controls such as below,

<StackPanel>
    <TextBox Name="TextBox1" TabIndex="1" />
    <MyUserControl TabIndex="2" />
    <TextBox Name="TextBox3" TabIndex="3" />
</StackPanel>

Your tabbing would change to TextBox1, TextBox3, TextBox2. This is because TextBox2 doesn't have a TabIndex specified, so the default is assumed and it is tabbed to after all the other controls with a TabIndex specified get cycled through.

The way I usually get around this is to bind the TabIndex of controls inside the UserControl to the UserControl.TabIndex.

For example adding the following binding to the UserControl would make the Tab cycle correct again

<MyUserControl>
    <TextBox Name="TextBox2" TabIndex="{Binding Path=TabIndex, RelativeSource={RelativeSource AncestorType={x:Type local:MyUserControl}}}" />
</MyUserControl>

I usually prefer to set this binding in the Loaded event of the UserControl instead of having to remember to set this binding on all the controls inside the UserControl. I'm sure there are also more efficient ways of doing this as well, however the problem has not come up often enough for me to sit down and take the time to research how to use tab scopes correctly to avoid this workaround.

10
votes

You should try setting a KeyboardNavigation.TabNavigation attached property on either your Tree control, or the StackPanel derived control, in case you want your bottom buttons to also participate in a tab cycle:

<controls:CustomStackPanel KeyboardNavigation.TabNavigation="Cycle">
 <Tree>
 ...
 </Tree>
</controls:CustomStackPanel>

You can even combine a code-behind approach that you, I assume, is currently trying to use to control the tab behaviour inside the Tree control, with the KeyboardNavigation.TabNavigation to take care of the tabbing outside the tree control.

5
votes

Not an answer per-se but WPF tabbing is extremely fiddly. Requires setting the TabNavigation, IsTabStop properties in Xaml and fiddling around with focus scope. I have spent days trying to get tabbing right using Xaml alone. I would suggest to begin with your XML -> WPF model really ought to be Xaml -> WPF! However I would imagine that's not possible.

How about this for a workaround. Is it possible for you to handle the tabbing in generated code? If your WPF usercontrols are generated by your own software from your own XML then I'd suggest putting a TabOrder element in your XML and then using that to wire up a TabOut event.

Look at the following code sample to force a move operation in code (Similar to Tabbing)

// Creating a FocusNavigationDirection object to perform the tab operation
// values include Next, Previous, First, Last etc...
FocusNavigationDirection focusDirection = FocusNavigationDirection.Next;

// MoveFocus takes a TraveralReqest as its argument.
TraversalRequest request = new TraversalRequest(focusDirection);

// Gets the element with keyboard focus.
UIElement elementWithFocus = Keyboard.FocusedElement as UIElement;

// Change keyboard focus.
if (elementWithFocus != null)
{
    elementWithFocus.MoveFocus(request);
}

Secondly wire in to the KeyUp (or PreviewKeyUp) event of your "tabbable" controls and if the key was tab call the above code to cause focus to jump to the next element.

The above code sample will basically force in code what WPF should do out of the box. As other posters have suggested I would go through your generated WPF code to see what the values of KeyboardNavigation.IsTabStop and KeyboardNavigation.TabNavigation

Best regards,

1
votes

Try KeyboardNavigation.TabNavigation="Once"