WPF tab order with custom controls?

前端 未结 5 1211
别那么骄傲
别那么骄傲 2021-01-14 13:46

I have a WPF page that contains several out of the box controls with the tab order set.

I have a custom control (NumericSpinner) that contains: border/grid/text box/

相关标签:
5条回答
  • 2021-01-14 13:46

    I don't have a complete solution for you, but the following four things should be checked for tab-cycling:

    • TabIndex defines the tab-order
    • IsTabStop says if the control should be used for tab-cycling
    • FocusManager.IsFocusScope says, if an element builds its own focus area
    • Focusable says if an UIElement can get the focus.

    I could imagine that the IsFocusScope would be interesting.

    0 讨论(0)
  • 2021-01-14 13:46

    Raul answered the primary issue - being able to tab into the text box of a custom control. The secondary problem was not being able to tab out of the text box.

    The issue with this control was that its textbox contains a keypress handler:

    PreviewKeyDown="valueText_PreviewKeyDown"
    

    The handler only allows certain keys to be pressed within the textbox:

    /// <summary>
    /// Since this event handler traps keystrokes within the control, in order to facilitate tabbing order, allowing the 
    /// tab key press must be enabled
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void valueText_PreviewKeyDown(object sender, KeyEventArgs e)
    { 
        KeyConverter converter = new KeyConverter();
        string key = converter.ConvertToString(e.Key);
        int index = ((TextBox)sender).CaretIndex;
        if (key != null)
        {
            if (AllowNegativeValues && (e.Key == Key.Subtract || e.Key == Key.OemMinus))
            {
                e.Handled = (valueText.Text.Contains('-') || index > 0) == true;
            }
            else if (AllowDecimal && (e.Key == Key.OemPeriod || e.Key == Key.Decimal))
            {
                e.Handled = valueText.Text.Contains('.') == true;
            }
            else
                e.Handled = ((((e.Key >= Key.D0) && (e.Key <= Key.D9) && (e.KeyboardDevice.Modifiers != ModifierKeys.Shift))
                                || ((e.Key >= Key.NumPad0) && (e.Key <= Key.NumPad9) && (e.KeyboardDevice.Modifiers != ModifierKeys.Shift))
                                || e.Key == Key.Left || e.Key == Key.Right
                                || e.Key == Key.Back || e.Key == Key.Delete 
                                || e.Key == Key.Tab) == false);             
        }
        else
            e.Handled = true;
    }
    

    I simply had to add the allowable TAB keystroke and the custom control works fine:

    e.Key == Key.Tab
    
    0 讨论(0)
  • 2021-01-14 13:47

    I have an answer to the first one... In your static constructor for your CustomControl add the following

    KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(NumericSpinner), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
    

    That should let you tab in and out of the control and allow you to set a tab index for each of the children of your custom control.

    As for your second question, I'm working on figuring out the same thing. I think it has to due with the fact that your custom control has Focusable = False. But setting this to true will make the control get focus not the actual children. I think what might be required is an event handler on your CustomControl for the GotFocus event. When the control gets focus then do a

    this.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
    

    and that should move the focus to the child elements.

    I'll post a follow up when I figure out the proper way of doing it.

    Update

    So for your second point.

    Make sure you set focusable to false and the TabNavigationProperty like so in the static constructor for your control

    FocusableProperty.OverrideMetadata(typeof(NumericSpinner), new FrameworkPropertyMetadata(false));
    KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(NumericSpinner), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
    

    This will allow the control to work as expected and respect the Tab Order on the control and also all the children. Make sure that in your style you don't set the Focusable or TabNavigation properties.

    Raul

    0 讨论(0)
  • 2021-01-14 13:54

    HaxElit's answer works unless we want to use labels to focus our control by Alt + hotkey like this:

    <!-- Alt + C selects numCount -->
    <Label Target="{Binding ElementName=numCount}">Elements _Count:</Label>
    <local:NumericSpinner x:Name="numCount"/>
    

    So my final solution is as follows:

    static NumericSpinner()
    {
        // Next line prevents focusing our wrapper parent control. The problem with that is that
        // it prevents Label controls to select our control by Alt+<hotkey>
        //FocusableProperty.OverrideMetadata(typeof(NumericSpinner), new FrameworkPropertyMetadata(false));
    
        // Next line specifies that the children controls have their own tab subtree so a deep
        // traversal is performed when our control is focused
        KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(NumericSpinner), new FrameworkPropertyMetadata(KeyboardNavigationMode.Local));
    }
    
    // our wrapper control is focused invisibly. We must relocate the focus.
    protected override void OnGotFocus(RoutedEventArgs e)
    {
        base.OnGotFocus(e);
    
        // Next line moves the focus either forward (to out first inner child)
        // or backward if Shift+TAB is pressed (to the previous control)
        MoveFocus(new TraversalRequest(Keyboard.IsKeyDown(Key.Tab) && (Keyboard.Modifiers & ModifierKeys.Shift) != 0 ? FocusNavigationDirection.Previous : FocusNavigationDirection.Next));
    }
    
    0 讨论(0)
  • 2021-01-14 14:11

    I've tried using the selected answer's GotFocus event, but it doesn't work for me, it seems to disable the tab navigation altogether, even though the first TabStop is indeed selected.
    What worked for me was using the WindowActivated event and use the same command there:

    MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
    

    I hope it might help someone.

    0 讨论(0)
提交回复
热议问题