我找到了一种方法,但必须有更好的方法。
通过 Mole 看 Expander,或者看 Blend 生成的 ControlTemplate,我们可以看到响应 Space/Enter/Click/etc 的 header 部分实际上是一个 ToggleButton。现在是个坏消息,因为 Header 的 ToggleButton 对 Expander 的 Expanded 属性 Up/Down/Left/Right 有不同的布局,它已经通过 Expander 的 ControlTemplate 分配了样式。这使我们无法做一些简单的事情,例如在 Expander 的资源中创建默认的 ToggleButton 样式。
如果您可以访问后面的代码,或者不介意将 CodeBehind 添加到扩展器所在的资源字典中,那么您可以访问 ToggleButton 并在 Expander.Loaded 事件中设置 TabIndex,如下所示:
<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;
}
}
如果您需要它与多个扩展器一起使用,您也可以将发送器对象转换为扩展器。另一种选择是为扩展器创建自己的 ControlTemplate 并在其中进行设置。
编辑
我们还可以将代码部分移动到 AttachedProperty,使其更清洁和更易于使用:
<Expander local:ExpanderHelper.HeaderTabIndex="20">
...
</Expander>
和附加属性:
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;
}
}
}