Это только косые черты, которые портят валидацию, вы можете кодировать их, используя %5C
, то есть шестнадцатеричное кодирование \
, или, как говорит Майк У, вы можете дважды убежать, как \\
, а затем вы можете просто декодировать их когда вы хотите их использовать
Следующий код будет работать даже без свойств 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>
Я нашел способ, но должно быть что-то получше.
Глядя на Expander через Mole или на его ControlTemplate, созданный Blend, мы видим, что часть заголовка, которая является ответ на пробел / ввод / щелчок / и т. д. действительно является ToggleButton. А теперь плохие новости. Поскольку ToggleButton заголовка имеет другой макет для свойств Expanded Expanded Up / Down / Left / Right, он уже имеет стили, назначенные ему через ControlTemplate Expander. Это мешает нам сделать что-то простое, например создать стиль ToggleButton по умолчанию в ресурсах Expander.
Если у вас есть доступ к исходному коду или вы не возражаете добавить CodeBehind в словарь ресурсов, в котором находится расширитель, тогда вы может получить доступ к ToggleButton и установить TabIndex в событии Expander.Loaded, Другой вариант - создать собственный ControlTemplate для расширителя (ов) и настроить его там.
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;
}
}
}