wpfでテキスト編集可のComboBoxを使っていて次の2通りのタイミングで処理がしたくなりました。
- ComboBoxについているTextBoxでエンターキーが押された。
- ComboBoxについているポップアップメニューのクリックで項目が選択された。
エンターキーはKeyBindingで検出するとして、問題はポップアップメニューの方です。 最初はSelectionChangedイベントを使えばいいのかと思ってたんですが、このイベント、けっこう色んなタイミングで発行されるんですよね。 具体的には次のタイミングで発行されるのを確認しました。
- TextBoxの内容がComboBoxItemの内容と一致。
- TextBoxの内容とComboBoxItemの内容とが一致した状態からTextBoxが書き換えられる。
- TextBoxにフォーカスをあわせ上下キーでComboBoxItemを選択。
- ポップアップメニューでComboBoxItemを選択。
TextBoxの書き換えのときはスルーして、エンターキーとポップアップメニュークリックにだけ反応させたかったのですが、そのようなイベントは用意されていませんでした。 自分で実装です。
ポップアップメニュークリックの検出について、具体的にどうするかと言うと、まずはComboBoxについているPopupを探します。 (注:ComboBoxには色んなPopupを付けられるようですが、今回はデフォルトのポップアップメニューで試しました。) こんなコードで見つかりました。
// 最初に見つかったコントロールを返す。
private DependencyObject FindControl(DependencyObject obj, Type controlType)
{
if (obj == null)
return null;
if (obj.GetType() == controlType)
return obj;
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
DependencyObject descendant = FindControl(child, controlType);
if (descendant != null && descendant.GetType() == controlType)
{
return descendant;
}
}
return null;
}
ComboBoxのLoadedイベントで
Popup popup = (Popup)FindControl(コンボボックス, typeof(Popup)) _comboBoxPopup = popup // ←メンバー変数に参照を記憶
と書けばOK。 ちなみに、引数に渡すタイプをTextBoxにすればTextBoxが見つかります。
そしてComboBoxのSelectionChangedイベントでPopupが開いているかチェックします。 開いているかのチェックはPopup.IsOpenで行います。 開いているときにSelectionChangedイベントが発行されたということは、Popupから選択されたということを表しています。
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_comboBoxPopup == null || !_comboBoxPopup.IsOpen)
return;
MessageBox.Show("コンボボックスのポップアップがクリックされた");
}
これで、色んなタイミングで発行されるSelectionChangedイベントの中からポップアップメニューのクリックだけを検出で切るようになりました。 目的達成です。 意外と時間が取られたような、でも短い時間で済んだような...
以下、サンプルコード全体を載せておきます。 まずはMainWindow.xaml
<Window x:Class="ComboBoxPopupClickTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:ComboBoxPopupClickTest"
Title="MainWindow" Height="350" Width="525">
<ComboBox
Name="testComboBox"
Loaded="OnComboBoxLoaded"
SelectionChanged="OnComboBoxSelectionChanged"
IsEditable="True"
Height="30"
VerticalAlignment="Top"
>
<ComboBox.CommandBindings>
<CommandBinding Command="{x:Static app:MainWindow.EnterKeyDownCommand}" Executed="OnEnterKeyDown"/>
</ComboBox.CommandBindings>
<ComboBox.InputBindings>
<KeyBinding Key="Enter" Command="{x:Static app:MainWindow.EnterKeyDownCommand}"/>
</ComboBox.InputBindings>
<ComboBoxItem>10</ComboBoxItem>
<ComboBoxItem>20</ComboBoxItem>
<ComboBoxItem>30</ComboBoxItem>
<ComboBoxItem>40</ComboBoxItem>
<ComboBoxItem>50</ComboBoxItem>
</ComboBox>
</Window>
そしてMainWindow.xaml.csです。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace ComboBoxPopupClickTest
{
public partial class MainWindow : Window
{
public readonly static RoutedCommand EnterKeyDownCommand = new RoutedCommand();
private Popup _comboBoxPopup;
public MainWindow()
{
InitializeComponent();
}
private void OnComboBoxLoaded(object sender, RoutedEventArgs e)
{
_comboBoxPopup = (Popup)FindControl(testComboBox, typeof(Popup));
}
private void OnEnterKeyDown(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("コンボボックスでエンターキーが押された");
}
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_comboBoxPopup == null || !_comboBoxPopup.IsOpen)
return;
MessageBox.Show("コンボボックスのポップアップがクリックされた");
}
// 最初に見つかったコントロールを返す。
private DependencyObject FindControl(DependencyObject obj, Type controlType)
{
if (obj == null)
return null;
if (obj.GetType() == controlType)
return obj;
int childrenCount = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
DependencyObject descendant = FindControl(child, controlType);
if (descendant != null && descendant.GetType() == controlType)
{
return descendant;
}
}
return null;
}
}
}