そこそこ、複雑なアプリケーションで、ショートカットキーを正しく処理する方法は意外に難解です。単純なアプリケーション用のより簡単な方法は c# ショートカットキーを正しく処理するには? (その3) をご覧ください。
最も美しい解決方法は、最も楽な方法では限りませんが、ある程度の大きさのアプリケーションを作る場合には、適切な方法です。この方法では、カーソルキーやアルファベットキーなど、修飾子なしのキーもショートカットキーとして使用できます。
ショートカットキーは、Form の ToolStripMenuItem の ShortcutKeys で指定します。デザイナーでビジュアルに指定できるので簡単です。この方法では、修飾子なしのショートカットキーが指定できないので、それらのキーは、Form の ProcessCmdKey をオーバーライドして処理します。ProcessCmdKey で処理を打ち切る場合には、true を、続ける場合には、base.ProcessCmdKey を呼び、その値を返します。
ShortcutKeys で指定したキーは、base.ProcessCmdKey で処理されます。メニューに処理させない場合やできない場合に、テキストだけ表示させるには、ShortcutKeyDisplayString を使います。
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (keyData == Keys.Escape) // ESC 単独 Clipboard.Clear(); return base.ProcessCmdKey(ref msg, keyData); }
アプリケーションが複数のビューからなり、フォーカスのあるビューによってショートカットの挙動を変えるには、各ビューのメニューや、ProcessCmdKey に Form と同様の処理を書けば OK です。
フォーカスのあるコントロールの ProcessCmdKey が最初に呼ばれるため、フォーカスにより挙動を変化させることができます。base.ProcessCmdKey により、親の ProcessCmdKey が呼ばれ、最終的には、 Form の ProcessCmdKey が呼ばれます。
例えば、Form の ProcessCmdKey で、Ctrl+V を処理して、true を返した場合、コンボボックスにフォーカスがあっても、コンボボックスのディフォルトの処理であるコピーが実行されません。
これを避けるには、ComboBox の代わりに派生クラスを使用します。派生クラスでは、ProcessCmdKey をオーバーライドして、Ctrl+V 等の場合、base.ProcessCmdKey を呼ばずに、false を返します。
false を返すと、Ctrl+V などのディフォルトのコマンドは、コンボボックスコントロールにより、処理されます。base.ProcessCmdKey を呼ばなければ、親コントロールの ProcessCmdKey が呼ばれなくなり、Form などで書いた処理をスキップできます。
コンボボックスで false を返すべきキーは、Ctrl+C、Ctrl+V、Ctrl+X、Ctrl+Z、Ctrl+Insert、Shift+Insert、Shift+Delete、Alt+Back です。また、Ctrl や Shift などの修飾の無いアクセラレーターキーを親で定義したい場合には、修飾子が無い場合、F1~F24、Escape 以外で、false を返すと良いでしょう。
また、Ctrl+A はディフォルトでは処理されないようですが、ここで SelectAll() してから、true を返すことで実現できます。
protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { Keys code = keyData & Keys.KeyCode; Keys modi = keyData & Keys.Modifiers; if (modi == Keys.Control) { switch (code) { case Keys.A: SelectAll(); return true; case Keys.C: case Keys.V: case Keys.X: case Keys.Z: case Keys.Insert: return false; } } else if (modi == Keys.Shift) { switch (code) { case Keys.Insert: case Keys.Delete: return false; } } else if (modi == Keys.Alt) { switch (code) { case Keys.Back: return false; } } else if (modi == Keys.None) { switch (code) { case Keys.F1: case Keys.F2: case Keys.F3: case Keys.F4: case Keys.F5: case Keys.F6: case Keys.F7: case Keys.F8: case Keys.F9: case Keys.F10: case Keys.F11: case Keys.F12: case Keys.F13: case Keys.F14: case Keys.F15: case Keys.F16: case Keys.F17: case Keys.F18: case Keys.F19: case Keys.F20: case Keys.F21: case Keys.F22: case Keys.F23: case Keys.F24: case Keys.Escape: break; default: return false; } } return base.ProcessCmdKey(ref msg, keyData); }
コンボボックスだけでなく、コントロールで必要なキーをショートカットキーで使用している場合には、それらのコントロールの ProcessCmdKey でも同様の処理を行います。
その2 では、コンボボックスなどのコモンコントロールを派生させずに利用できるので、ちょっと楽できるかもしれませんね。
結論から言えば、ProcessDialogKey はアクセラレーターキーを処理するために用意されたメソッドでは無いので、おすすめしません。
ProcessDialogKey は、 PreProcessMessage 内で、 ProcessCmdKey の次に呼ばれる性質の似たメソッドです。ディフォルトでは、フォーカスのあるコントロールから親コントロールへ向かって順に ProcessCmdKey が呼ばれた後に、同様に呼ばれますが、以下の点で違いがあります。
例えば、Form のメニューにショートカットキーを設定した場合、Form の基底クラスの ProcessCmdKey で処理されます。子コントロールにフォーカスがある場合に挙動を変えたくても、ProcessDialogKey では遅すぎますし、IsInputKey で、true を返しても、ProcessCmdKey は呼ばれます。特に Ctrl+V を定義する場合、コンボボックスの Ctrl+V の機能を復活させるには、結局 ProcessCmdKey のオーバーライドが必要です。
例えば、コンボボックスではカーソルキーの IsInputKey は true を返します。結果、コンボボックスにフォーカスがある場合、ProcessDialogKey は呼ばれないので、カーソルキーによりフォーカスの遷移も実行されません。
この挙動が便利だという理由で、このメソッドでショートカットキーを実現するのをすすめているサイトが多いですが、それほどのメリットとは思えません。もちろん、コンボボックスのカーソルキーの挙動を置き換えたい場合には逆に面倒です。
また、ディフォルトでは、IsInputKey は、Tab、Return、Esc、カーソルキー の状態しか判定しないようです。例えば、コンボボックスではカーソルキーで操作が可能なので、true を返しますが、同じく重要な A などの文字では false を返します。
ProcessDialogKey では、A などのキーは利用しないので、これが正しい挙動なんでしょうが、コンボボックスにフォーカスが無い場合に、A などをアクセラレーターキーで使用するには、結局、コンボボックスの ProcessDialogKey をオーバーライドするか、IsInputKey をオーバーライドするか、OnPreviewKeyDown 系で IsInputKey を true に設定する必要があります。複雑ですね・・・。
@IT Windowsアプリケーションで方向キーなどの特殊キーを処理するには? の、カーソルキーによるフォーカス移動の挙動を変更する例のように、Tab、Return、Esc、カーソルキーによる、ダイアログの挙動を変更する場合には、適切な修正箇所と言えます。
OnKeyDown で実装する方法は、ProcessDialogKey よりもましですが、ProcessCmdKey によりオーバーライドされやすいので、オーバーライドされてもかまわないようなキー操作を実現するのに向いています。
ちなみに、ミルノ PC フォトフレーム では、カーソルキーによるフォーカス移動は必要なしと判断、ショートカットキーとして利用しています。
この場合、コンボボックス、タブコントロール、ツリービューでカーソルキーが重要な動作をするので、これらの ProcessCmdKey では、base.ProcessCmdKey を呼ばずに、false を返すことで、本来の動きを維持しています。
※ 厳密には、タブコントロールを派生するのは面倒だったので、タブコントロールの親コントロールの ProcessCmdKey で、タブにフォーカスがある場合に、false を返しています。
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。