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; } } }