Как установить TabIndex для элемента управления WPF Expander? - PullRequest
6 голосов
/ 13 июня 2009

В этом примере окно табуляции переходит от первого текстового поля к последнему текстовому полю и затем к заголовку расширителя.

<Window x:Class="ExpanderTab.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300"
    FocusManager.FocusedElement="{Binding ElementName=FirstField}">
    <StackPanel>
        <TextBox TabIndex="10" Name="FirstField"></TextBox>
        <Expander TabIndex="20" Header="_abc">
            <TextBox TabIndex="30"></TextBox>
        </Expander>
        <TextBox TabIndex="40"></TextBox>
    </StackPanel>
</Window>

Очевидно, я бы хотел, чтобы это было в первом текстовом поле, в заголовке расширителя, а затем в последнем текстовом поле. Есть ли простой способ присвоить TabIndex заголовку расширителя?

Я пытался заставить экспандер быть табуляцией, используя KeyboardNavigation.IsTabStop="True", но это заставляет весь экспандер получить фокус, и весь экспандер не реагирует на пробел. После еще двух вкладок заголовок снова выбирается, и я могу открыть его с помощью пробела.

Edit: Я дам награду за каждого, кто может придумать более чистый способ сделать это - если нет, то rmoore, вы можете иметь представителя. Спасибо за вашу помощь.

Ответы [ 2 ]

10 голосов
/ 16 июня 2009

Следующий код будет работать даже без свойств TabIndex, они включены для ясности об ожидаемом порядке табуляции.

<Window x:Class="ExpanderTab.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300" FocusManager.FocusedElement="{Binding ElementName=FirstField}">
    <StackPanel>
        <TextBox TabIndex="10" Name="FirstField"></TextBox>
        <Expander TabIndex="20" Header="Section1" KeyboardNavigation.TabNavigation="Local">
            <StackPanel KeyboardNavigation.TabNavigation="Local">
                <TextBox TabIndex="30"></TextBox>
                <TextBox TabIndex="40"></TextBox>
            </StackPanel>
        </Expander>
        <Expander TabIndex="50" Header="Section2" KeyboardNavigation.TabNavigation="Local">
            <StackPanel KeyboardNavigation.TabNavigation="Local">
                <TextBox TabIndex="60"></TextBox>
                <TextBox TabIndex="70"></TextBox>
            </StackPanel>
        </Expander>
        <TextBox TabIndex="80"></TextBox>
    </StackPanel>
</Window>
3 голосов
/ 13 июня 2009

Я нашел способ, но должно быть что-то лучше.


Глядя на Expander через Mole или просматривая его ControlTemplate, сгенерированный Blend, мы видим, что часть заголовка, отвечающая на пробел / ввод / щелчок / и т. Д., Действительно является ToggleButton. Теперь плохие новости, поскольку ToggleButton заголовка имеет разную компоновку для расширенных свойств Expander вверх / вниз / влево / вправо, у него уже есть стили, назначенные ему через ControlTemplate экспандера. Это не позволяет нам делать что-то простое, например, создавать стиль ToggleButton по умолчанию в ресурсах Expander.

alt text

Если у вас есть доступ к коду позади или вы не против добавить CodeBehind в словарь ресурсов, в котором находится расширитель, то вы можете получить доступ к ToggleButton и установить TabIndex в событии Expander.Loaded, например так:

<Expander x:Name="uiExpander"
          Header="_abc"
          Loaded="uiExpander_Loaded"
          TabIndex="20"
          IsTabStop="False">
    <TextBox TabIndex="30">

    </TextBox>
</Expander>


private void uiExpander_Loaded(object sender, RoutedEventArgs e)
{
    //Gets the HeaderSite part of the default ControlTemplate for an Expander.
    var header = uiExpander.Template.FindName("HeaderSite", uiExpander) as Control;
    if (header != null)
    {
        header.TabIndex = uiExpander.TabIndex;
    }
}

Вы также можете просто привести объект-отправитель к Expander, если он вам нужен для работы с несколькими экспандерами. Другой вариант - создать собственный ControlTemplate для Expander и установить его там.

EDIT Мы также можем переместить часть кода в AttachedProperty, что делает его намного чище и проще в использовании:

<Expander local:ExpanderHelper.HeaderTabIndex="20">
    ...
</Expander>

и свойство AttachedProperty:

public class ExpanderHelper
{
    public static int GetHeaderTabIndex(DependencyObject obj)
    {
        return (int)obj.GetValue(HeaderTabIndexProperty);
    }

    public static void SetHeaderTabIndex(DependencyObject obj, int value)
    {
        obj.SetValue(HeaderTabIndexProperty, value);
    }

    // Using a DependencyProperty as the backing store for HeaderTabIndex.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty HeaderTabIndexProperty =
        DependencyProperty.RegisterAttached(
        "HeaderTabIndex",
        typeof(int),
        typeof(ExpanderHelper),
        new FrameworkPropertyMetadata(
            int.MaxValue,
            FrameworkPropertyMetadataOptions.None,
            new PropertyChangedCallback(OnHeaderTabIndexChanged)));

    private static void OnHeaderTabIndexChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var expander = o as Expander;
        int index;

        if (expander != null && int.TryParse(e.NewValue.ToString(), out index))
        {
            if (expander.IsLoaded)
            {
                SetTabIndex(expander, (int)e.NewValue);
            }
            else
            {
                // If the Expander is not yet loaded, then the Header will not be costructed
                // To avoid getting a null refrence to the HeaderSite control part we
                // can delay the setting of the HeaderTabIndex untill after the Expander is loaded.
                expander.Loaded += new RoutedEventHandler((i, j) => SetTabIndex(expander, (int)e.NewValue));
            }
        }
        else
        {
            throw new InvalidCastException();
        }
    }

    private static void SetTabIndex(Expander expander, int index)
    {
        //Gets the HeaderSite part of the default ControlTemplate for an Expander.
        var header = expander.Template.FindName("HeaderSite", expander) as Control;
        if (header != null)
        {
            header.TabIndex = index;
        }
    }
}
...