メニュー関連の前の記事: WPFでのメニューとキーボード操作時のフォーカス移動の話
この記事ではContextMenuについてだけ記述していますが、Menuでも同様の現象が発生しますし同様の方法で対処可能です。
現象
ContextMenuを定義し、MenuItemにアクセスキーを指定します。
<Window x:Class="WpfTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow"> <Window.ContextMenu> <ContextMenu> <MenuItem Header="ファイルを開く(_O)" Click="MenuItem_Click" /> </ContextMenu> </Window.ContextMenu> </Window>
Clickイベント発生の確認にMessageBoxでも出しておきます。
using System.Windows; namespace WpfTest { public partial class MainWindow { public MainWindow() { InitializeComponent(); } private void MenuItem_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Click"); } } }
このコードをビルドして実行すると、次のことに気付きます。
- IMEの状態にかかわらず、右クリックやShift+F10でコンテキストメニューが開く
- IMEがOFF状態なら、oキーを押すとアクセスキーが発動する
- IMEがON状態なら、oキーを押してもIME変換が入り'お'になるのでアクセスキーが発動しない
- IMEがON状態でも、Altキーを押しながらShift+F10でコンテキストメニューを開いてoキーを押すとアクセスキーが発動する
普通のItemsControl(と言うよりSelector?)なら TextSearch Class で特定の要素にアクセスできるため、IMEがONでも意味はあります。
しかしMenuItemには大体アクセスキーを設定するでしょうからIMEは全くの無用です。
そういうわけで、ContextMenuを開いた時にIMEを無効状態にしましょう。
属性構文で設定する
InputMethod.PreferredImeState Attached Property を設定すれば、ContextMenuを開いた時にIMEを無効化出来ます。
<!--前略--> <ContextMenu InputMethod.PreferredImeState="Off"> <!--中略--> </ContextMenu> <!--後略-->
ただしContextMenuを閉じた後でもIME状態がOFFのままになっています。
TextSearchを使用しているListBoxItemなどのContextMenuでは、IME状態を復元したいところです。
また、ContextMenuを開いている間に半角/全角キーや変換キーを押すとIMEを有効にできてしまいます。
なお InputMethod.IsInputMethodSuspended Attached Property と InputMethod.IsInputMethodEnabled Attached Property も試しましたが、ContextMenuに設定しても効果がありませんでした。
イベントで処理する
ContextMenuは、開いた時にキーボードフォーカスを得て、閉じた時にキーボードフォーカスを失います。カーソルキーなどを押しているとMenuItem間でキーボードフォーカスが移動します。
UIElement.IsKeyboardFocusWithinChanged Event という今回の目的に合うイベントがあるので、そこで状態の保存復元をしましょう。
キー入力によるIME有効化を防ぐ処理も入れてしまいましょう。(TextBox の IME を無効にする - (憂国のプログラマ Hatena版 改め) 周回遅れのブルース を参考にしました。)
<Window x:Class="WpfTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow"> <Window.ContextMenu> <ContextMenu IsKeyboardFocusWithinChanged="ContextMenu_IsKeyboardFocusWithinChanged" PreviewKeyDown="ContextMenu_PreviewKeyDown"> <MenuItem Header="ファイルを開く(_O)" Click="MenuItem_Click" /> </ContextMenu> </Window.ContextMenu> </Window>
using System.Windows; using System.Windows.Input; namespace WpfTest { public partial class MainWindow { // コンストラクタやClickイベントは省略 private InputMethodState _lastState; private void ContextMenu_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) { var inputMethod = InputMethod.Current; if (((UIElement)sender).IsKeyboardFocusWithin) { this._lastState = inputMethod.ImeState; inputMethod.ImeState = InputMethodState.Off; } else { inputMethod.ImeState = this._lastState; } } private void ContextMenu_PreviewKeyDown(object sender, KeyEventArgs e) { e.Handled = (e.Key == Key.ImeProcessed); } } }
Behaviorにする
IME状態の保存・復元のためだけの状態をWindowに持たせたくないので、Expression Blend SDKの Behavior Generic Class を継承して自作Behaviorにしてしまいましょう。
<Window x:Class="WpfTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:local="clr-namespace:WpfTest" Title="MainWindow"> <Window.ContextMenu> <ContextMenu> <i:Interaction.Behaviors> <local:TemporaryDisableImeBehavior /> </i:Interaction.Behaviors> <MenuItem Header="ファイルを開く(_O)" Click="MenuItem_Click" /> </ContextMenu> </Window.ContextMenu> </Window>
using System.Windows; using System.Windows.Input; using System.Windows.Interactivity; namespace WpfTest { public class TemporaryDisableImeBehavior : Behavior<UIElement> { private InputMethodState _lastState; protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.IsKeyboardFocusWithinChanged += AssociatedObject_IsKeyboardFocusWithinChanged; this.AssociatedObject.PreviewKeyDown += AssociatedObject_PreviewKeyDown; } protected override void OnDetaching() { this.AssociatedObject.IsKeyboardFocusWithinChanged -= AssociatedObject_IsKeyboardFocusWithinChanged; this.AssociatedObject.PreviewKeyDown -= AssociatedObject_PreviewKeyDown; base.OnDetaching(); } private void AssociatedObject_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e) { var inputMethod = InputMethod.Current; if (((UIElement)sender).IsKeyboardFocusWithin) { this._lastState = inputMethod.ImeState; inputMethod.ImeState = InputMethodState.Off; } else { inputMethod.ImeState = this._lastState; } } private void AssociatedObject_PreviewKeyDown(object sender, KeyEventArgs e) { e.Handled = (e.Key == Key.ImeProcessed); } } }