Порядок вкладок WPF с пользовательскими элементами управления?

У меня есть страница WPF, которая содержит несколько готовых элементов управления с установленным порядком табуляции.

У меня есть настраиваемый элемент управления (NumericSpinner), который содержит: границу/сетку/текстовое поле/2 кнопки повтора (вверх/вниз).

Две проблемы:

1) когда я нахожусь в текстовом поле для пользовательского элемента управления селектором, я не могу перейти от него к другим элементам управления на странице. Однако после нажатия на одну из стрелок вверх/вниз я могу перейти к другим элементам управления.

2) Я не могу перейти в текстовое поле пользовательского элемента управления по порядку. Только после того, как я перешел через все элементы управления, курсор попадает в текстовое поле (и не может выйти из него).

Контекст:

<ComboBox Margin="97,315,21,0" Name="txtdweldatcdu" Style="{StaticResource fieldComboBoxStyle}" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" VerticalAlignment="Top" TabIndex="10" />
    <WpfControls:NumericSpinner Margin="97,338,21,0" Name="txtdweldatpctcomplete" HorizontalAlignment="Left" VerticalAlignment="Top" AllowNegativeValues="True" MaxValue="100" TabIndex="11" />
    <ComboBox Margin="97,363,21,0" Name="txtdweldatclass" Style="{StaticResource fieldComboBoxStyle}" IsSynchronizedWithCurrentItem="True" HorizontalAlignment="Left" VerticalAlignment="Top" TabIndex="12" />

Часть пользовательского элемента управления:

 <Border BorderThickness="1" BorderBrush="Gray" Margin="0" HorizontalAlignment="Left" VerticalAlignment="Top" Height="20" Width="117">
        <Grid Margin="0">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="98"/>
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBox Name="valueText" 
                     BorderThickness="0" 
                     Grid.RowSpan="2"
                     Style="{StaticResource spinnerTextBoxStyle}"
                     PreviewKeyDown="valueText_PreviewKeyDown"
                     PreviewTextInput="valueText_PreviewTextInput"
                     TextChanged="valueText_TextChanged"
                     IsReadOnly="{Binding ElementName=Spinner, Path=IsReadOnly}"
                     Text="{Binding ElementName=Spinner, Path=Value, Mode=TwoWay}"
                     KeyboardNavigation.IsTabStop="True"
                     AcceptsTab="True"/>
            <RepeatButton Name="upButton" Style="{StaticResource spinnerRepeatButtonStyle}" Click="upButton_Click"  Grid.Column="1" Grid.Row="0" Height="10" Width="18" VerticalAlignment="Top" HorizontalAlignment="Right" HorizontalContentAlignment="Center">
                <Polygon  HorizontalAlignment="Center" Points="3,2 2,3 4,3"  Fill="Black"  Stretch="Uniform"  Stroke="Black"  StrokeThickness="0" />
            </RepeatButton>
            <RepeatButton Name="downButton" Style="{StaticResource spinnerRepeatButtonStyle}" Click="downButton_Click"  Grid.Column="1" Grid.Row="1" Height="10" Width="18" VerticalAlignment="Top" HorizontalAlignment="Right" HorizontalContentAlignment="Center">
                <Polygon  HorizontalAlignment="Center" Points="2,2 4,2 3,3"  Fill="Black"  Stretch="Uniform"  Stroke="Black"  StrokeThickness="0" />
            </RepeatButton>
        </Grid>
    </Border>

Пользовательский элемент управления состоит из файла xaml и кода программной части.

Родительская страница xaml, содержащая все элементы управления, загружается динамически и не содержит кода программной части.

В конструкторе пользовательского элемента управления я установил следующее в качестве теста:

    valueText.TabIndex = 3;
    this.TabIndex = 3;

В четвертый раз, когда я вхожу, я фактически помещаю курсор в текстовое поле, однако не могу выйти из него.

Имея это в виду, первым шагом будет создание параметра элемента управления, которому я могу передать номер порядка табуляции, который будет установлен в коде элемента управления.

Я создал свойство CustomTabIndex:

/// <summary>
/// Custom tab index property
/// </summary>
public int CustomTabIndex
{
    get { return (int)GetValue(CustomTabIndexProperty); }
    set { SetValue(CustomTabIndexProperty, value); }
}

public static readonly DependencyProperty CustomTabIndexProperty = 
    DependencyProperty.Register("CustomTabIndex", typeof(int), typeof(NumericSpinner));

И в xaml, когда я пытаюсь установить CustomTabIndex="3", я получаю сообщение об ошибке:

Свойство «CustomTabIndex» не найдено в типе «NumericSpinner».

Некоторая помощь будет оценена по достоинству.


person ElHaix    schedule 18.01.2011    source источник


Ответы (5)


У меня есть ответ на первый... В вашем статическом конструкторе для вашего CustomControl добавьте следующее

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

Это должно позволить вам входить и выходить из элемента управления и устанавливать индекс вкладки для каждого из дочерних элементов вашего пользовательского элемента управления.

Что касается вашего второго вопроса, я работаю над тем, чтобы выяснить то же самое. Я думаю, это связано с тем, что ваш пользовательский элемент управления имеет Focusable = False. Но если установить для этого параметра значение true, элемент управления получит фокус, а не фактические дочерние элементы. Я думаю, что может потребоваться обработчик событий в вашем CustomControl для события GotFocus. Когда элемент управления получает фокус, выполните

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

и это должно переместить фокус на дочерние элементы.

Я опубликую продолжение, когда выясню, как правильно это сделать.

Обновить

Итак, по вашему второму пункту.

Убедитесь, что вы установили для фокуса значение false и TabNavigationProperty, например, в статическом конструкторе для вашего элемента управления.

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

Это позволит элементу управления работать должным образом и соблюдать порядок вкладок в элементе управления, а также во всех дочерних элементах. Убедитесь, что в вашем стиле не установлены свойства Focusable или TabNavigation.

Рауль

person HaxElit    schedule 20.01.2011
comment
Рауль, спасибо. первый ответ позволяет мне теперь перейти в текстовое поле пользовательского элемента управления, однако я все еще не могу выйти из него. Выполнение FocusableProperty.OverrideMetadata, похоже, не имеет никакого значения. Когда я нажимаю на первую стрелку (следующий элемент управления после текстового элемента управления), я могу перейти к следующему элементу управления, а затем из пользовательского элемента управления к следующему элементу управления страницы. - person ElHaix; 21.01.2011
comment
Решение последнего заключается в следующем: Textbox содержит обработчик нажатия клавиши (PreviewKeyDown), который не допускает использование клавиши Tab. Я просто добавил e.Key == Key.Tab в раздел допустимых кодов нажатия клавиш, и все готово. - person ElHaix; 21.01.2011

У меня нет полного решения для вас, но следующие четыре вещи должны быть проверены на цикличность вкладок:

  • TabIndex определяет порядок табуляции
  • IsTabStop говорит, следует ли использовать элемент управления для переключения вкладок
  • FocusManager.IsFocusScope говорит, что если элемент строит свою собственную зону фокусировки
  • Focusable говорит, может ли UIElement получить фокус .

Я мог представить, что IsFocusScope будет интересен.

person HCL    schedule 18.01.2011

Рауль ответил на основную проблему — возможность вкладки в текстовое поле пользовательского элемента управления. Второстепенная проблема заключалась в невозможности вывести табуляцию из текстового поля.

Проблема с этим элементом управления заключалась в том, что его текстовое поле содержит обработчик нажатия клавиши:

PreviewKeyDown="valueText_PreviewKeyDown"

Обработчик позволяет нажимать только определенные клавиши в текстовом поле:

/// <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;
}

Мне просто нужно было добавить допустимое нажатие клавиши TAB, и пользовательский элемент управления отлично работает:

e.Key == Key.Tab
person ElHaix    schedule 21.01.2011

Я пытался использовать событие GotFocus выбранного ответа, но оно не работает для меня, кажется, оно полностью отключает навигацию по вкладкам, даже если первый TabStop действительно выбран.
Что сработало для меня, так это использование события WindowActivated и используйте там ту же команду:

MoveFocus(new TraversalRequest(FocusNavigationDirection.First));

Я надеюсь, что это может помочь кому-то.

person Noich    schedule 06.12.2011

Ответ HaxElit работает, если мы не хотим использовать метки для фокусировки нашего контроля с помощью Alt + горячая клавиша следующим образом:

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

Итак, мое окончательное решение выглядит следующим образом:

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));
}
person György Kőszeg    schedule 16.06.2016