c# ショートカットキーを正しく処理するには?

そこそこ、複雑なアプリケーションで、ショートカットキーを正しく処理する方法は意外に難解です。単純なアプリケーション用のより簡単な方法は c# ショートカットキーを正しく処理するには? (その3) をご覧ください。

最も美しい解決方法

最も美しい解決方法は、最も楽な方法では限りませんが、ある程度の大きさのアプリケーションを作る場合には、適切な方法です。この方法では、カーソルキーやアルファベットキーなど、修飾子なしのキーもショートカットキーとして使用できます。

アプリケーション全体のショートカット (その2と共通)

ショートカットキーは、Form の ToolStripMenuItemShortcutKeys で指定します。デザイナーでビジュアルに指定できるので簡単です。この方法では、修飾子なしのショートカットキーが指定できないので、それらのキーは、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);
}

フォーカスに依存したショートカット (その2と共通)

アプリケーションが複数のビューからなり、フォーカスのあるビューによってショートカットの挙動を変えるには、各ビューのメニューや、ProcessCmdKey に Form と同様の処理を書けば OK です。

フォーカスのあるコントロールの ProcessCmdKey が最初に呼ばれるため、フォーカスにより挙動を変化させることができます。base.ProcessCmdKey により、親の ProcessCmdKey が呼ばれ、最終的には、 Form の ProcessCmdKey が呼ばれます。

コンボボックスにおける注意点 (Ctrl+V など)

例えば、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との比較

その2 では、コンボボックスなどのコモンコントロールを派生させずに利用できるので、ちょっと楽できるかもしれませんね。

ProcessDialogKey を利用する方法との比較

結論から言えば、ProcessDialogKey はアクセラレーターキーを処理するために用意されたメソッドでは無いので、おすすめしません。

ProcessDialogKey は、 PreProcessMessage 内で、 ProcessCmdKey の次に呼ばれる性質の似たメソッドです。ディフォルトでは、フォーカスのあるコントロールから親コントロールへ向かって順に ProcessCmdKey が呼ばれた後に、同様に呼ばれますが、以下の点で違いがあります。

ProcessCmdKey で true を返すと呼ばれない

例えば、Form のメニューにショートカットキーを設定した場合、Form の基底クラスの ProcessCmdKey で処理されます。子コントロールにフォーカスがある場合に挙動を変えたくても、ProcessDialogKey では遅すぎますし、IsInputKey で、true を返しても、ProcessCmdKey は呼ばれます。特に Ctrl+V を定義する場合、コンボボックスの Ctrl+V の機能を復活させるには、結局 ProcessCmdKey のオーバーライドが必要です。

IsInputKey で true を返すと呼ばれない (PreviewKeyDown 系も同様)

例えば、コンボボックスではカーソルキーの IsInputKey は true を返します。結果、コンボボックスにフォーカスがある場合、ProcessDialogKey は呼ばれないので、カーソルキーによりフォーカスの遷移も実行されません。

この挙動が便利だという理由で、このメソッドでショートカットキーを実現するのをすすめているサイトが多いですが、それほどのメリットとは思えません。もちろん、コンボボックスのカーソルキーの挙動を置き換えたい場合には逆に面倒です。

また、ディフォルトでは、IsInputKey は、Tab、Return、Esc、カーソルキー の状態しか判定しないようです。例えば、コンボボックスではカーソルキーで操作が可能なので、true を返しますが、同じく重要な A などの文字では false を返します。

ProcessDialogKey では、A などのキーは利用しないので、これが正しい挙動なんでしょうが、コンボボックスにフォーカスが無い場合に、A などをアクセラレーターキーで使用するには、結局、コンボボックスの ProcessDialogKey をオーバーライドするか、IsInputKey をオーバーライドするか、OnPreviewKeyDown 系で IsInputKey を true に設定する必要があります。複雑ですね・・・。

ProcessDialogKey をオーバーライドするのが適切な例

@IT Windowsアプリケーションで方向キーなどの特殊キーを処理するには? の、カーソルキーによるフォーカス移動の挙動を変更する例のように、Tab、Return、Esc、カーソルキーによる、ダイアログの挙動を変更する場合には、適切な修正箇所と言えます。

OnKeyDown を利用する方法との比較

OnKeyDown で実装する方法は、ProcessDialogKey よりもましですが、ProcessCmdKey によりオーバーライドされやすいので、オーバーライドされてもかまわないようなキー操作を実現するのに向いています。

実装例

ちなみに、ミルノ PC フォトフレーム では、カーソルキーによるフォーカス移動は必要なしと判断、ショートカットキーとして利用しています。

この場合、コンボボックス、タブコントロール、ツリービューでカーソルキーが重要な動作をするので、これらの ProcessCmdKey では、base.ProcessCmdKey を呼ばずに、false を返すことで、本来の動きを維持しています。

※ 厳密には、タブコントロールを派生するのは面倒だったので、タブコントロールの親コントロールの ProcessCmdKey で、タブにフォーカスがある場合に、false を返しています。

となりのページ

このサイトについて

このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。

連絡先

ここをクリック すると表示されるページから作者へメールで連絡できます。

共有