- ベストアンサー
コントロールキーが押されたキー入力の判定方法
BCB5を使って、WinXP用のプログラムを作っています。 キー入力で、コントロールキーが押されているのか、を判定する処理が必要になりました。 OnKeyDownイベントのTShiftStateを判定すれば良いようですが、これの具体的な記述方法が分かりません。 単純に、 if(Shift == ssCtrl) という判定ではダメのようです。 TShiftStateというのはどの様に判定すれば良いのでしょうか?
- みんなの回答 (8)
- 専門家の回答
質問者が選んだベストアンサー
>VK_AやVK_0は「未定義」となって、コンパイルが通りません。 失礼。winuser.hを見ると /* VK_0 thru VK_9 are the same as ASCII '0' thru '9' (0x30 - 0x39) */ /* VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' (0x41 - 0x5A) */ と書いてあって、定義が無いようです。 自前のヘッダファイルに #define VK_0 0x30 #define VK_1 0x31 #define VK_2 0x32 #define VK_3 0x33 #define VK_4 0x34 #define VK_5 0x35 #define VK_6 0x36 #define VK_7 0x37 #define VK_8 0x38 #define VK_9 0x39 #define VK_A 0x41 #define VK_B 0x42 #define VK_C 0x43 #define VK_D 0x44 #define VK_E 0x45 #define VK_F 0x46 #define VK_G 0x47 #define VK_H 0x48 #define VK_I 0x49 #define VK_J 0x4A #define VK_K 0x4B #define VK_L 0x4C #define VK_M 0x4D #define VK_N 0x4E #define VK_O 0x4F #define VK_P 0x50 #define VK_Q 0x51 #define VK_R 0x52 #define VK_S 0x53 #define VK_T 0x54 #define VK_U 0x55 #define VK_V 0x56 #define VK_W 0x57 #define VK_X 0x58 #define VK_Y 0x59 #define VK_Z 0x5A などと定義しておくか if (Key == '0') とか if (Key == 'A') とか、直接、文字定数と比較すると良いでしょう。 定義が無い、って事は、直接'0'~'9'や'A'~'Z'と比較しても構わない、と言うことだと推測されます。 >Key == 0x79 はF10として認識されましたが、 >Key == 0x61 はaとして認識されませんでした。 仮想キーコードには大文字も小文字も区別がありません。 >従って、Ctrl + a が認識できない状態です。 もしかして「Ctrl+A」と「Ctrl+a」を「別のもの、違うものとして判定」したいのでしょうか? 上記の通り、英字キーは「すべて、大文字の'A'から'Z'に相当するコード」が割り当てられています。 なので「A」と「a」の区別はありません。同様に「Ctrl+A」と「Ctrl+a」の区別もありません。 そして、小文字の'a'から'z'に相当するコードには、以下のように「全然違うキー」が割り当てられています。 #define VK_NUMPAD1 0x61 #define VK_NUMPAD2 0x62 #define VK_NUMPAD3 0x63 #define VK_NUMPAD4 0x64 #define VK_NUMPAD5 0x65 #define VK_NUMPAD6 0x66 #define VK_NUMPAD7 0x67 #define VK_NUMPAD8 0x68 #define VK_NUMPAD9 0x69 #define VK_MULTIPLY 0x6A #define VK_ADD 0x6B #define VK_SEPARATOR 0x6C #define VK_SUBTRACT 0x6D #define VK_DECIMAL 0x6E #define VK_DIVIDE 0x6F #define VK_F1 0x70 #define VK_F2 0x71 #define VK_F3 0x72 #define VK_F4 0x73 #define VK_F5 0x74 #define VK_F6 0x75 #define VK_F7 0x76 #define VK_F8 0x77 #define VK_F9 0x78 #define VK_F10 0x79 #define VK_F11 0x7A 従って、OnKeyDownでは「A」と「a」は単純には区別できません。同様に「Ctrl+A」と「Ctrl+a」も単純には区別できません。 小文字が入力されたのか大文字が入力されたのかは、以下のように判定します。 ・他のAPI関数でCapsLockの状態を調べて、CapsLockがオフの場合 「A」の判定:Shist.Contains(ssShift)が真、かつ、Keyが0x41('A') 「a」の判定:Shist.Contains(ssShift)が偽、かつ、Keyが0x41('A') ・他のAPI関数でCapsLockの状態を調べて、CapsLockがオンの場合 「A」の判定:Shist.Contains(ssShift)が偽、かつ、Keyが0x41('A') 「a」の判定:Shist.Contains(ssShift)が真、かつ、Keyが0x41('A') このように、CapsLockがオンかオフか、AltやCtrlやShiftキーが一緒に押されているかどうかに関わらず「Aの文字が刻印されたキーの仮想キーコードは常に0x41('A')」なので、色々と面倒な判定が必要です。 もし「Ctrl+AやCtrl+a」を調べたいなら「Ctrlキー以外の特殊キーが一緒に押されていたら、どう扱うか」も考えなければなりません。 例えば Ctrlキーだけ+A⇒○ Altキー+Ctrlキー+A⇒× Shiftキー+Ctrlキー+A⇒× Altキー+Shiftキー+Ctrlキー+A⇒× のようにしたいなら、AltキーとShiftキーの状態も判定しないとならないでしょう。
その他の回答 (7)
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
>Ctrl + A が押されたか?という判定で、 >if(Shift.Contains(ssCtrl) == true && Key == 'A'){ >というif文では不十分でしょうか? それでも可。 >この判定直後でMessageBoxを表示し、「***の処理をしても良いですか」 >という記述をした場合に、Crtl + A が連続して押される、という状況は >免れると思います。 その代わり、ボタンの方に「Crtl + Aが離された」ってイベントが飛んで行かないので、色々と問題が出るかも知れない。問題が出ないかも知れない。 キーを離したのを待たない場合、キーが押されたままフォーカスがメッセージボックスに移るので、もし、メッセージボックスの方に「Crtl + Aが離されたら、何か特殊処理をする」とかって特殊処理が施してあれば「想定外の処理が想定外のタイミングで発生」する事になる。 もちろん、ボタンの方も「Crtl + Aが離された」ってイベントが飛んで来ないので「何時までも押しっぱなし」と勘違いして、場合に拠っては「キーボードに反応しなくなる」とかの問題が出るかも知れない。問題が出ないかも知れない。 こればっかりは「やってみないと判らない」です。 >それから、この例では、ボタンのTagにはゼロが格納されている、という前提なのですよね? 設計時、フォーム上にボタンを配置し、オブジェクトインスペクタでボタンの各種プロパティを表示させ「Tag」プロパティを見ると値はゼロになっている筈。それを手で書き換えない限り、実行開始直後のTagプロパティは0。 >if ((Key == 'A') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { >このif文とビット操作が良く分からないのです。 「Keyが'A'で、かつ、fにBKEY_CTRLとBMOUSE_CTRLのどれか1つ以上がオンになっているならば、真」 こういう書き方は「各ビットに意味を持たせた時に使う定石」なので、覚えておく事。 ・定石1 「AかBかCのうち、どれか1つ以上がオンなら真」 (フラグ & (A | B | C)) または ((フラグ & (A | B | C)) != 0) ・定石2 「AとBとCのうち、どれか1つ以上がオフなら真」 ((フラグ & (A | B | C)) != (A | B | C)) ・定石3 「AとBとCが、全部オンの時だけ真」 ((フラグ & (A | B | C)) == (A | B | C)) ・定石4 「AとBとCが、全部オフの時だけ真」 (!(フラグ & (A | B | C))) または ((フラグ & (A | B | C)) == 0) ・定石5 「A、B、Cが特定の組み合わせの時だけ真」 ((フラグ & (A | B | C)) == (A)) //Aがオン、BとCがオフなら真 ((フラグ & (A | B | C)) == (B)) //Bがオン、AとCがオフなら真 ((フラグ & (A | B | C)) == (C)) //Cがオン、AとBがオフなら真 ((フラグ & (A | B | C)) == (A | B)) //AとBがオン、Cがオフなら真 ((フラグ & (A | B | C)) == (A | C)) //AとCがオン、Bがオフなら真 ((フラグ & (A | B | C)) == (B | C)) //BとCがオン、Aがオフなら真 ・定石6 「Aのフラグを付ける」 フラグ |= A; ・定石7 「Aのフラグを消す」 フラグ &= ~A; 上記の定石は、ビットが3種類で説明したけど、ビットの種類が幾つあっても書き方の基本は同じ。 定石の中に必ず出て来る「フラグ & (A | B | C)」の部分は「フラグのビットのAとBとCについてのみ調べたい」と言う意味になってる。この部分のおかげで「フラグに付いてるDやGやJのビットを無視」している。 で、余計なビットを無視した上で「じゃ、無視しなかったA、B、Cの状態はどうなってる?」ってのを比較演算子「==」や「!=」を使って判断している。 慣れれば「A、B、Cのどれか1つでもオンで、かつ、D、E、Fが全部オンの時に真、または、G、H、IのうちHとIがオンでHがオフの時に真」とかって判定式も書けるようになる。 >最初の二つと、最終的に判定しているBCTRL_Aはビットの位置が違うのでは?と混乱しています。 ビット位置違って当たり前。 それぞれのビットは BKEY_CTRLは「OnKeyDownに来た時にCtrlキーが押されていればオン」 BMOUSE_CTRLは「OnMouseDownに来た時にCtrlキーが押されていればオン」 BCTRL_Aは「1度でもCtrl+Aが押されていたならオン」 と言う意味を持っている。 3つとも意味が違うんだから、同じビット位置にしちゃ駄目だってのは判りますか?
お礼
丁寧な回答、ありがとうございました。 本題からズレた話題になりますが、ビット操作の定石、良く分かりました。 私は事務処理専門なので、この辺の知識はほとんどなく、説明してもらえて良かったです。 if (Key == VK_CONTROL) f |= BKEY_CTRL; は、Ctrlキーが押されていたら、fの最下位ビット(以下、第1ビット)に1を立てる、という意味だと理解しました。 また、Ctrlと一緒にボタンが押されたときは、BMOUSE_CTRLなので、第4ビットに1を立てています。 if ((Key == 'A') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { これは「Aが押され、なおかつ、fの第1ビットまたは第4ビットに1が立っている時」という判定だと思います。 ところが、連続入力の判定は、BCTRL_A(0x40)なので、第7ビットに1が立っているか?という記述になっています。 セットするときは、第1、第4ビットなのに、その結果を判定するときは第7ビットを見ている、となり、判定位置が違うのでは?と思った次第です。 でも、よく見ると if ((Key == 'A') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { の最後で、第7ビットに1を立てていますね。 これはKeyUpされると解除される訳ですね。 この文章を書き始めるまでは良く分かっていなかったのですが、質問を書き始めたら分かり始めました。 お蔭様でした。
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
>if (f & BCTRL_A) { >これで「押され続けている」という判定になる理由が良く分かりませんでした。 その直前に if ((Key == 'A') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { がありますね。 これは「押されたキーがAで、Ctrlキーを押していたフラグが付いている時」に成り立ちます。 その次の if (f & BCTRL_A) { が成り立つのは「Ctrl+Aを押したとのフラグが、もう既に付いていた時」です。 このフラグはOnKeyUpで消される筈なので、通常であれば「OnKeyDown(フラグ付く)⇒OnKeyUp(フラグ消える)⇒OnKeyDown(フラグ付く)⇒OnKeyUp(フラグ消える)…」の順にイベントが起こり「OnKeyDownに来た時はフラグが付いてない筈」です。 しかし、キーリピートが働くとOnKeyDownだけが連続で発生します。 そのため「OnKeyDownが2度以上続けて起きた時」には「2度目のOnKeyDownに来た時には、1度目のOnKeyDownで付けておいたフラグが、付いたままになっている」のです。 従って if (f & BCTRL_A) { は「キーリピートが始まって、2度以上連続してOnKeyDownに来た時」に成り立ちます。 「キーリピートが始まって、2度以上連続してOnKeyDownに来た時」とは「押され続けている時」の事です。 そして、このif文の判定が終われば、もう1度目か2度目以降なのか気にしなくて良いので f |= BCTRL_A; を行って、フラグを付けています。 簡単に言えば「フラグを付ける前に、もう既にフラグが付いてたのなら、2回連続でフラグを付けようとしたって事」です。
お礼
回答ありがとうございました。 まず、本題から...。 Ctrl + A が押されたか?という判定で、 if(Shift.Contains(ssCtrl) == true && Key == 'A'){ というif文では不十分でしょうか? この判定直後でMessageBoxを表示し、「***の処理をしても良いですか」という記述をした場合に、Crtl + A が連続して押される、という状況は免れると思います。 それでもchie65535さんがおっしゃる記述をしないと危険でしょうか? ButtonのMouseDownとClickを組み合わせなさい、という話は良く分かりました。 それから、この例では、ボタンのTagにはゼロが格納されている、という前提なのですよね? 次に脱線の話題で...。 if ((Key == 'A') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { このif文とビット操作が良く分からないのです。 BKEY_CTRLは0x01なので、 0000 0001 BMOUSE_CTRLは0x08なので、 0000 1000 BCTRL_Aは0x40なので、 0100 0000 最初の二つと、最終的に判定しているBCTRL_Aはビットの位置が違うのでは?と混乱しています。 この辺の話はC言語の初心者向けの話題かもしれません。 (f & (BKEY_CTRL | BMOUSE_CTRL)) を日本語で説明してくれると分かるかも知れません。
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
追記。 先ほどの例は、新規プロジェクトを作り、Form1フォームを開いて、ボタンをButton1の1つ、ラベルをLabel1~Label4の4つを配置して、Button1のイベントのOnKeyUp、OnKeyDown、OnMouseDown、OnMouseUp、OnClickを設定し、Unit1.cppに掲載したソースをコピーペーストで追記すれば動きます。
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
>Button1MouseDownイベントで判定すれば良さそうですね。 >よく確認せずに思いつきの質問をしてしまい申し訳ありませんでした。 「ボタンがクリックに反応する時」は「マウスが離された時」なのでOnMouseDownではフラグを付けるだけにとどめ、処理の本体はOnClickで処理すべきです。 そして、OnMouseDownで付けたフラグは、OnMouseUpで後始末(クリア)するようにしましょう。 もし「マウスが押された時にCtrlキーも一緒に押されてるのを調べて何かの処理をして、その後に発生する筈のOnClickで後始末の処理をする」というプログラムを組むと酷い目に遭います。 やってみると判るのですが、以下のような操作をした場合、OnClickが発生しません。 ・ボタンコントロールの上でマウスの左ボタンを押す(押したまま離さない)この時点でOnMouseDown発生。 ・マウスの左ボタンを押したまま、マウスを動かして、マウスカーソルをボタンコントロールから外す。 ・マウスの左ボタンを離す。この時点でOnMouseUp発生。 もし「押した瞬間に処理が始まってしまう」というプログラムをすると、上記のような「ボタン押しちゃったけど、やっぱやめた。今の無し」と言う操作が出来なくなります。 また、Ctrl+Aの判定時も、キーリピートにより「OnKeyDownが連続して発生し、OnKeyUpが発生しない」と言う状態がありますので注意してください。 もし「OnKeyDownのあとには必ずOnKeyUpがある筈」と言うプログラムをしたら酷い目に遭います。 「クリックした時に、Ctrlキーなども一緒に押されていたか判定する」とか「Ctrl+AやCtrl+Bを判定する」と言う場合は、以下のようにします。 #define BKEY_CTRL 0x01 #define BKEY_SHIFT 0x02 #define BKEY_ALT 0x04 #define BMOUSE_CTRL 0x08 #define BMOUSE_SHIFT 0x10 #define BMOUSE_ALT 0x20 #define BCTRL_A 0x40 #define BCTRL_B 0x80 //--------------------------------------------------------------------------- void __fastcall TForm1::Button1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift) { int f = ((TComponent *)(Sender))->Tag; if (Key == VK_SHIFT) f |= BKEY_SHIFT; if (Key == VK_CONTROL) f |= BKEY_CTRL; if (Key == VK_MENU) f |= BKEY_ALT; if ((Key == 'A') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { if (f & BCTRL_A) { Label4->Caption = "Ctrl+Aが押され続けている"; } else { Label4->Caption = "Ctrl+Aが押された"; } f |= BCTRL_A; } if ((Key == 'B') && (f & (BKEY_CTRL | BMOUSE_CTRL))) { if (f & BCTRL_B) { Label4->Caption = "Ctrl+Bが押され続けている"; } else { Label4->Caption = "Ctrl+Bが押された"; } f |= BCTRL_B; } ((TComponent *)(Sender))->Tag = f; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1KeyUp(TObject *Sender, WORD &Key, TShiftState Shift) { int f = ((TComponent *)(Sender))->Tag; if (Key == VK_SHIFT) f &= ~BKEY_SHIFT; if (Key == VK_CONTROL) f &= ~BKEY_CTRL; if (Key == VK_MENU) f &= ~BKEY_ALT; if (Key == 'A') { if (f & BCTRL_A) { Label4->Caption = "Ctrl+Aが離された"; } f &= ~BCTRL_A; } if (Key == 'B') { if (f & BCTRL_B) { Label4->Caption = "Ctrl+Bが離された"; } f &= ~BCTRL_B; } ((TComponent *)(Sender))->Tag = f; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { int f = ((TComponent *)(Sender))->Tag; f &= ~(BMOUSE_CTRL | BMOUSE_SHIFT | BMOUSE_ALT); if (Shift.Contains(ssCtrl)) f |= BMOUSE_CTRL; if (Shift.Contains(ssShift)) f |= BMOUSE_SHIFT; if (Shift.Contains(ssAlt)) f |= BMOUSE_ALT; ((TComponent *)(Sender))->Tag = f; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { int f = ((TComponent *)(Sender))->Tag; f &= ~(BMOUSE_CTRL | BMOUSE_SHIFT | BMOUSE_ALT); ((TComponent *)(Sender))->Tag = f; } //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { int f = ((TComponent *)(Sender))->Tag; if (f & (BKEY_CTRL | BMOUSE_CTRL)) { Label1->Caption = "Ctrlキーを押されてクリック"; } else { Label1->Caption = "Ctrlキーを押されずクリック"; } if (f & (BKEY_SHIFT | BMOUSE_SHIFT)) { Label2->Caption = "Shiftキーを押されてクリック"; } else { Label2->Caption = "Shiftキーを押されずクリック"; } if (f & (BKEY_ALT | BMOUSE_ALT)) { Label3->Caption = "Altキーを押されてクリック"; } else { Label3->Caption = "Altキーを押されずクリック"; } } //--------------------------------------------------------------------------- この例では、フラグに「コンポーネントのTagプロパティ」を使用しているので、グローバル変数は不要です。 また、この例は、複数のボタンでイベントコードを共有出来ます。こうするとボタンコントロールごとにイベントを1つづつ個別に書く必要はないので「1ヶ所だけ直し忘れた」と言うミスを防げます。
お礼
う~ん、ここまでしないとマズイですか? 単純ではなかったのですね。 レベルが高くてちょっとついて行けない状態です。 if (f & BCTRL_A) { これで「押され続けている」という判定になる理由が良く分かりませんでした。 Ctrlとボタンについての説明は分かりました。 工夫してみようと思います。 ありがとうございました。
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
if ((Shist.Contains(ssCtrl)) && (Key >= VK_A) && (Key <= VK_Z)) { Application->MessageBox("仮想キーコードの英字キーはVK_A~VK_Zで判定する。","Ctrl+英字キーが押された",MB_OK|MB_ICONQUESTION); } if ((Shist.Contains(ssCtrl)) && (Key >= VK_0) && (Key <= VK_9)) { Application->MessageBox("仮想キーコードのフルキー側の数字キーはVK_0~VK_9で判定する。","Ctrl+フルキー側の数字キーが押された",MB_OK|MB_ICONQUESTION); } if ((Shist.Contains(ssCtrl)) && (Key >= VK_NUMPAD0) && (Key <= VK_NUMPAD9)) { Application->MessageBox("仮想キーコードのテンキー側の数字キーはVK_0~VK_9で判定する。但しNumLockが押されていないとテンキー側の数字キーはVK_NUMPAD0~VKNUMPAD9にならない。","Ctrl+テンキー側の数字キーが押された",MB_OK|MB_ICONQUESTION); }
お礼
chie65535さん、ありがとうございました。 しかし、残念ながらうまく動きませんでした。 私はBorland C++ Builder5 を使っていますが、VK_AやVK_0は「未定義」となって、コンパイルが通りません。 コンパイラーの違いによるものでしょうか? ちなみにOnKeyDownイベントでは、 Key == 0x79 はF10として認識されましたが、 Key == 0x61 はaとして認識されませんでした。 従って、Ctrl + a が認識できない状態です。
- chie65536(@chie65535)
- ベストアンサー率44% (8741/19839)
if (Shist.Contains(ssCtrl)) { Application->MessageBox("TShiftStateはtypedef Set<Classes__1, ssShift, ssDouble> TShiftState;と定義されているので、Setクラス型のContainsメソッドで要素の有無を判定する。詳しくはSetクラスのヘルプを参照する事。\r\n但し、OnKeyDownでは「Ctrlキーだけ」が押されてもイベントが発生しないので、Ctrlキーのみが押されたのは判断できない。","Ctrlキーも押された",MB_OK|MB_ICONQUESTION); }
- Yanch
- ベストアンサー率50% (114/225)
この辺のサイトとか参考になりませんか。 http://rein.dip.jp/cgi-bin/tips-01/wforum.cgi?no=25&reno=no&oya=25&mode=msgview&page=40
お礼
ありがとうございました。 参考になりました。 ところで、「Ctrl + a」のような判定はOnKeyDownイベントでは出来ないのでしょうか? Ctrl + F1のような、VK_*** は判定できました。 charはどのようにしたら判定出来るでしょうか? 追加の質問で申し訳ありませんがよろしくお願いします。
お礼
すばらしい!ありがとうございました。 今まで全然気が付かなかったのですが、OnKeyDownイベントでは、 Key == 'a'は認識しないのですが Key == 'A'は認識するのですね! 大文字小文字の区別は必要ありません。 このキーが認識されれば良いのです。 if(Shift.Contains(ssCtrl) == true && Key == 'A'){ 以下略 これで希望通りになりました。 大文字小文字を区別する例も良く分かりました。 (CapsLockを調べたり、結構面倒ですね。) この回答で充分なのですが、ついでにもう一つ知っていたら教えて欲しいことが出来ました。 ボタンをクリックした時に、コントロールキーやシフトキーが押されているか、という判定は出来ますか? 単純には、ボタンのOnKeyDownイベントに同様の記述をすれば良いと思うのですが、ボタンにフォーカスがあるときは、Ctrlキーを押した段階で反応してしまいます。(当然と言えば当然ですが・・・。) 元々、Windowsの仕様にないことならあきらめますが、可能でしょうか?
補足
下の「お礼」の追加です。 ボタンを押したときに、Ctrlキーを判定するには、 Button1MouseDownイベントで判定すれば良さそうですね。 よく確認せずに思いつきの質問をしてしまい申し訳ありませんでした。 おかげさまでとても役に立ちました。