はじめに
WPFで開発をしている時に、コントロールによってタブでフォーカスが移らないようにIsTabStopをFalseにすることがあります。
ところが、ComboBoxでIsTabStopをFalseにしても、IsEditableをTrueにしてしまうと、タブで移動してしまうという現象が発生します。
開発当時、まだWPFを勉強し始めたころは、もうチンプンカンプンで、何が起きているのかわかりませんでした。
とりあえず慌ててググッて、対策を・・・ということをしてきましたが、今なら落ちついて(笑)考察できるようになったので、原因と対策を書き留めておこうと思います。
原因
ComboBoxでIsTabStopを設定した場合、当然ComboBox自体にこのプロパティがセットされるのですが、どうやらIsEditable=Trueにすると、ComboBox内のPART_EditableTextBoxが使用されるみたいです。
この時問題なのが、ComboBoxのControlTemplateで、MicrosoftのComboBox のスタイルとテンプレートをみるとこうなっています。
<TextBox x:Name="PART_EditableTextBox"
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="3,3,23,3"
Focusable="True"
Background="Transparent"
Visibility="Hidden"
IsReadOnly="{TemplateBinding IsReadOnly}" />
なんと、IsTabStopの項目がないです。
だから、IsEditable=Trueにすると、IsTabStopはもう関係なしということで、何をしても必ずタブが移ってしまいます。
対策1 ControlTemplate
一つ目の対策としては、Window.Resourcesとか、App.xamlでリソースを設定して、ControlTemplateを書きます
//~中略~
<TextBox x:Name="PART_EditableTextBox"
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Margin="3,3,23,3"
Focusable="True"
Background="Transparent"
Visibility="Hidden"
IsReadOnly="{TemplateBinding IsReadOnly}"
IsTabStop="{Binding IsTabStop, RelativeSource={RelativeSource TemplatedParent}}"/>
//~中略~
でも、これだととにかく長いリソースを書かないといけないのでめんどいです。
以前、他にもいじりたいところがあったので、ComboBoxExというカスタムコントロールを作って、そこでGeneric.xamlにリソースを書いて利用しました。
だけど、本当に面倒でした。また新規に開発するときに・・・と思うと嫌です(~_~;)
対策2 コードビハインド
二つ目の対策としては、コードビハインドに書いてしまうという方法です。
Window_Loadedに以下のようなコードを書きます。
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var textbox = combobox1.Template.FindName("PART_EditableTextBox", combobox1) as TextBox;
if (textbox != null)
textbox.IsTabStop = combobox1.IsTabStop;
}
これで、ComboBoxのIsTabStopと連携して、タブで移動してこなくなります。
対策3 ビヘイビア(Behavior)
最後に、ビヘイビアを作るという方法です。
コードビハインドと同じようなコードでBehaviorを作ります
public class ComboBoxBehavior : Behavior<ComboBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectLoaded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObjectLoaded;
}
void AssociatedObjectLoaded(object sender, System.Windows.RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
var textBox = comboBox.Template.FindName("PART_EditableTextBox", comboBox) as TextBox;
if (textBox == null) return;
textBox.IsTabStop = comboBox.IsTabStop;
}
}
でこれをコントロールに当てはめる
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behavior="clr-namespace:ControlTest.Behaviors" // この名前は任意で
<ComboBox x:Name="combobox1"
ItemsSource="{Binding Items}"
IsEditable="True"
IsTabStop="False">
<i:Interaction.Behaviors>
<behavior:ComboBoxBehavior/>
</i:Interaction.Behaviors>
</ComboBox>
だいたいこんな感じでうまくいきました。
何か他にも方法をご存じの方がいましたら、教えていただけるとありがたいです。