Windowsフォームアプリケーション(.NET Framework)でショートカットキーを実装する方法を紹介します。
ショートカットキーの実装方法
サンプルとして以下の仕様を実装する方法を紹介します。
フォーム上のどのコントロールにフォーカスが当てられていても、
- [Ctrl]+[Shift]+[T]が同時押しされたことを識別する
- [Ctrl]+[T]が押されたことを識別する
※[Ctrl]+[T]を押して、1の条件をキャッチするようなことはないようにする。
手順1:FormのKeyPreviewプロパティをTrueにする。
手順2:FormのKeyDownイベントに、下記のような構文を書く。
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
//[Ctrl]+[Shift]+[T]が押されたらキャッチする
if (e.KeyData == (Keys.T | Keys.Shift | Keys.Control))
{
MessageBox.Show("Ctrl + Shift + T");
}
//[Ctrl]+[T]が押されたらキャッチする
if (e.KeyData == (Keys.T | Keys.Control))
{
MessageBox.Show("Ctrl + T");
}
}
以上でOKです。
以下個人メモ
ここから先は個人的なメモや考察などになります。
殴り書きになっている箇所もありますのでご了承ください。
FormのKeyPreviewプロパティ
FormのKeyPreviewプロパティをTrueにすることによって、Form以外のコントロールにフォーカスがある状態でキーを押したとしても、Formのキーイベントも起動されるようになります。※PreviewKeyDownは除く

また、上記の処理でForm1_KeyDownイベントの中でe.Handledをtrueにしたときは、ritchTextBoxのKeyDownイベントは起きなくなります。

e.KeyData
データ構成
KeyDownイベント(恐らくKeyUpなどでも同じ)のe.KeyDataは下記のようなデータ構成になっています。
- 「キーコード」(AキーやBキーなど)と「修飾子」(CtrlキーやShiftキーなど)を組み合わせた値
- 「キーコード」は1つ、修飾子は0以上(ない場合もある)
なので、e.KeyDataを使ってキーが押されたときにキャッチするためには、「キーコード」と「修飾子」を組み合わせて条件を作らなければならない。条件を作る際はKeys列挙型を使えば作りやすい。
OK例:[Ctrl] + [Shift] + [Alt] + [T]を押したときの条件は下記のような感じになる。※修飾子は順不同で押してよいが、[T]はキーコードとして判別させるため必ず最後に押さなければキャッチできないので注意。
if (e.KeyData == (Keys.T | Keys.Alt | Keys.Shift | Keys.Control))
{
MessageBox.Show("Ctrl + Shift + Alt + T");
}
NG例:下記のようにキーコードを二つ組み合わせて作ってもうまく動作しない。
if (e.KeyData == (Keys.F1 | Keys.F2))
{
MessageBox.Show("F1 + F2");
}
Keys列挙型の修飾子に関する注意
Keys列挙型の修飾子には、「修飾子」としての値と「キーコード」としての値がそれぞれあるので注意する必要がある。
例えば、Ctrlキーは、「修飾子」としての値と、「キーコード」としての値がそれぞれ存在する。
- 「修飾子」としてのCtrlは『Keys.Control』
- 「キーコード」としてのCtrlは『Keys.ControlKey』
例1:Ctrlキーを押したことを判定する
Ctrlキーを押したときに発生するKeyDownイベントでのe.KeyDataの構成は、修飾子は『Keys.Control』、キーコードは『Keys.ControlKey』となる。そのため、それぞれの論理和がe.KeyDataと一致するときに条件を作る。
if (e.KeyData == (Keys.Control | Keys.ControlKey))
{
MessageBox.Show("Ctrl");
}
または、キーコード『Keys.ControlKey』をe.KeyCodeと比較するようにしても良い。e.KeyCodeにはKeyDownイベントでの「キーコード」だけが格納されている。
if (e.KeyCode == Keys.ControlKey)
{
MessageBox.Show("Ctrl");
}
例2:Ctrl + Shift を押したかどうかを判定する
Case1:[Shift]が修飾キーとなるよう先に押し、次に [Ctrl]がキーコードとなるよう押したときにキャッチする。
if (e.KeyData == (Keys.ControlKey | Keys.Shift | Keys.Control))
{
MessageBox.Show("Shift → Ctrl");
}
Case2:まず[Ctrl]が修飾キーとなるよう先に押す。次に[Shift]がキーコードにするために次に押したときにキャッチする。
if (e.KeyData == (Keys.ShiftKey | Keys.Shift | Keys.Control))
{
MessageBox.Show("Ctrl → Shift");
}
例3:[Ctrl]+[Alt]+[Shift]が押されたことを判定する
まず、[Ctrl]+[Alt]を修飾キーとなるよう先に押す(順不同)。次に、[Shift]がキーコードとなるよう次に押す。
if (e.KeyData == (Keys.ShiftKey | Keys.Shift | Keys.Control | Keys.Alt))
{
MessageBox.Show("Ctrl + Alt → Shift");
}
※Altをキーコードに設定するための列挙型は用意されていないように思える。
Keys.AltKeyのような値がKeys列挙型の中に用意されていない。
KeyPressイベント
キーが押されたときにイベントが走る。修飾子が押されたときにはイベントは発生しない。[Shift]+[T]を押してKeyPressイベントが起動したときに、eにどのような値が格納されているのか確認してみたのが下記。KeyDownと比べeが保有するプロパティは少ない。
KeyPressはこれから何が入力されようとしているのかを取得するときに便利だと思う(一方でKeyDownは何のキーが押されたのかを取得するようなイメージだと思う)。

KeyUpイベント
キーが離れたときに発生する。[Shift]+[T]を使ったときになどは、KeyUpイベントは2回発生する。
- 1回目→TとShiftがKeyDataに格納されている
- 2回目→最初に離した方ではないキーがKeyDataに格納されている
よって、KeyDataには離したキーではなく、離す前に押されているキーが格納されているといえる。
KeyDownの場合は、連続して押しっぱなしにすると何度もイベントが発生するのに対し、KeyUpは最後にキーを離したときにだけ発生する。


PreviewKeyDownとKeyDownの違い
現状で分かっている特徴のメモとなります。
- FormのPreviewをTrueにした状態で、Form以外のコントロール上でキーを押したときにFormのKeyDownよりも前にキーを押したコントロールのPreviewKeyDownが起動する。
- Tabキーを押したときにキャッチする(TabはKeyUpでもキャッチはできる)
keyPress、PreviousKeyDown、keyDown、keyUpの起動タイミングを確認
下記のようにリッチテキストボックスを配置し、リッチテキストボックスに対しkeyPress、PreviousKeyDown、keyDown、keyUpイベントを設定し、リッチテキストボックスにキーを入力して挙動を確認しました。

確認1:aキーを押し、キーをすぐに離す

確認2:aキーを押しっぱなしにする
PreviousKeyDown→KeyDown→KeyPressの順でループする。

確認3:[Shift]+[A]を同時押しする(Shiftの方がが若干先に押す)
下記のようになる。

最初のPreviewKeyDownは→KeyDownはShiftとしてイベントが走り、次のPreviewKeyDown→KeyDown→KeyPressはShift+Aとしてイベントが走っていると思われる。
確認4:[Shift]+[A]をずっと押し続ける
PreviewKeyDown→KeyDownの後、PreviewKeyDown→KeyDown→KeyPressがループし続ける。

最初のPreviewKeyDown→KeyDownは[Shift]としてイベントが発生し、以降のPreviewKeyDown→KeyDown→KeyPressは[Shift]+[A]の同時押しとしてイベントが発生していると思われる。
確認5:Shiftキーを押し、キーを離す
KeyPressは発生しない

確認6:Shiftキーを押しっぱなしにする
PreviousKeyDown→KeyDownの順でループする。※KeyPressは発生しない

確認7:Tabキーを押す
PreviousKeyDownとKeyUpイベントだけが発生する。

Tabはコントロールのフォーカスを変更するために使うものでもあるのでKeyDownは発生していないと思われる。