WPF Expander コントロールに TabIndex を設定するにはどうすればよいですか?
-
13-09-2019 - |
質問
このウィンドウの例では、タブ移動により最初のテキストボックスから最後のテキストボックスに移動し、次にエキスパンダーヘッダーに移動します。
<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"
, 、しかし、これによりエキスパンダー全体がフォーカスを取得し、エキスパンダー全体がスペースバーに反応しなくなります。さらに 2 つのタブを開くと、ヘッダーが再び選択され、スペースバーでヘッダーを開くことができます。
編集:もっときれいな方法を思いついた人には賞金を出します。そうでない場合は、担当者に連絡してください。ご協力いただきありがとうございます。
解決
の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>
他のヒント
方法は見つけましたが、もっと良い方法があるはずです。
Mole を通じて Expander を見るか、Blend によって生成される ControlTemplate を見ると、Space/Enter/Click/etc に応答するヘッダー部分が実際には ToggleButton であることがわかります。ここで悪いニュースです。ヘッダーの ToggleButton は、Expander の Expanded プロパティ Up/Down/Left/Right とは異なるレイアウトを持っているため、Expander の ControlTemplate を通じてスタイルがすでに割り当てられています。そのため、Expander のリソースでデフォルトの ToggleButton スタイルを作成するなどの単純なことを行うことができなくなります。
コードビハインドにアクセスできる場合、またはエキスパンダーが含まれるリソース ディクショナリにコードビハインドを追加しても構わない場合は、次のように 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;
}
}
複数のエクスパンダで動作する必要がある場合は、センダー オブジェクトをエクスパンダにキャストすることもできます。もう 1 つのオプションは、Expander 用に独自の ControlTemplate を作成し、そこに設定することです。
編集コード部分を 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;
}
}
}