アプリ版:「スタンプのみでお礼する」機能のリリースについて

チェックボックスのキーダウン時に矢印キーを検知する方法について

VB6のチェックボックスについて質問させて頂きます。

環境
  Windows 2000 SP4
  Visual Basic 6.0(SP6)

チェックボックスのキーダウン時に矢印キーを検知する方法について、ご存知の方がいらっしゃいましたら、ぜひ教えて頂きたいです。

自分なりに調べてみた結果は以下の通りです。

チェックボックスのKeyDownイベントでは矢印キーは検知できませんでした。

次に試したのが、サブクラス化を行い、WM_KEYDOWNを取得する方法です。

正直、サブクラス化を行えば矢印キーを確実に検知できると考えていたのですが、なぜか矢印キーを押してもWM_KEYDOWNメッセージを取得できず、このため矢印キーの検知ができませんでした。
矢印キー以外のキー、例えばAやSなどは問題なく検知できました。

ちなみに、この状態でSpy++を起動し、矢印キーを押したときのメッセージを確認してみると、チェックボックスウィンドウはきちんとWM_KEYDOWNを受け取っていました。
それなのにサブクラス化した方ではWM_KEYDOWNは取得できません。

まだ完全にサブクラス化の概念を理解している訳ではないため、恐らく何か原因があるのでしょうが、想像ができないでいます。
( Windowsから送られるメッセージがチェックボックスウィンドウに届くまでの間に誰がメッセージを処理してるのかが分かりません。自分は間に何もないと考えていました )。

この動作についても知っている方がいたら、説明して頂けるととても助かります。

すいませんが、ご教授お願い致します。

A 回答 (8件)

_

この回答への補足

文字数が足りないので、分割させて頂きます。

以下に自分が調査したときに使用したサブクラス化のサンプルコードを添付します。

1. プロジェクトにフォームとモジュールを追加し、フォームにチェックボックスを1つ追加する。
2. 以下コードをフォームに貼り付ける。

Option Explicit

Private Sub Form_Load()

SubClass Me.Check1.hWnd

End Sub

Private Sub Form_Unload(Cancel As Integer)

UnSubClass Me.Check1.hWnd

End Sub

3. 以下コードをモジュールに貼り付ける。

Option Explicit

Private Const GWL_WNDPROC = (-4)
Private Const WM_KEYDOWN = &H100

Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
ByVal hWnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long _
) As Long

Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" ( _
ByVal lpPrevWndFunc As Long, _
ByVal hWnd As Long, _
ByVal Msg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long _
) As Long

Private Declare Function SetProp Lib "user32" Alias "SetPropA" ( _
ByVal hWnd As Long, _
ByVal lpString As String, _
ByVal hData As Long _
) As Long

Private Declare Function GetProp Lib "user32" Alias "GetPropA" ( _
ByVal hWnd As Long, _
ByVal lpString As String _
) As Long

補足日時:2010/02/05 19:03
    • good
    • 0

_

この回答への補足

Private Const PROPNAME As String = "OriginWindowProc"

Public Sub SubClass(hWnd As Long)

Dim DefProc As Long

DefProc = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WindowProc)

If DefProc <> 0 Then
Call SetProp(hWnd, PROPNAME, DefProc)
End If

End Sub

Public Sub UnSubClass(hWnd As Long)

Dim DefProc As Long

DefProc = GetProp(hWnd, PROPNAME)

If DefProc <> 0 Then
SetWindowLong hWnd, GWL_WNDPROC, DefProc
End If

End Sub

Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Dim DefProc As Long

Select Case uMsg
Case WM_KEYDOWN
Debug.Print "KeyDown : KeyCode( " & wParam & " )"
End Select

DefProc = GetProp(hWnd, PROPNAME)

If DefProc <> 0 Then
WindowProc = CallWindowProc(DefProc, hWnd, uMsg, wParam, lParam)
End If

End Function

4. イミディエイトウィンドウに押したキーが表示されるが、矢印キーは表示されないことが確認できる
5. Spy++で確認すると、WM_KEYDOWNが発生していることが確認できる

補足日時:2010/02/05 19:10
    • good
    • 0

参考URL


http://okwave.jp/qa/q4164908.html


SetWindowsHookEx
UnhookWindowsHookEx
CallNextHookEx


フック関数「SetWindowsHookEx」
の呼び出し引数は「WH_CBT」となっているけどそこを変更して
「WH_KEYBOARD=2」

「WH_KEYBOARD_LL=13」
でいけるかな?

.NET用だけど、殆ど一緒です。
http://azumaya.s101.xrea.com/wiki/index.php?%B3% …


あと、不要なキーボードイベントもくるので、チェックボックスにカーソルがあるときの判定を、しなくてはいけません。
アクティブコントールのハンドルを取得するなどしてやるようにですね。

質問者さんのスキルは高そうなので、多くは言いません。
    • good
    • 0
この回答へのお礼

早速のご回答、ありがとうございます。
大変参考になります。

やはりフックを使用する方法しかないですよね。

記載して頂いた通り、フックは目的のコントロールを判定してやらなければならないので、できればサブクラス化でできないかなと思い、はまってしまった状態でした。

というのは、いまチェックボックスをベースにしたユーザコントロールを作成していまして、既存のチェックボックスのキーダウンイベントでは矢印キーを検知できないので、サブクラス化を利用し、矢印キーも検知できるキーダウンイベントを実装していました。

このため、好みの問題かもしれませんが、できればフックよりサブクラス化の方がしっくりくるかなと思い、現状に至っています。

ちなみに、このコントロールはグリッド専用のチェックボックスという目的で使用を考えてまして、チェックボックスなどを使用できるグリッドコントロールも同時に作成しています。

グリッドのため、方向キーでの移動を実装しなければならず、このためチェックボックスでの方向キーを検知したかったのでした。

打開策としては、フォントでチェックボックスっぽく見せる方法や、チェックボックスのコンテナをピクチャボックスとし、ちょこっと制御することで、方向キーを検知する方法などが見つかりましたが、なんでサブクラス化じゃできないかが、どうしても分からず、凄く気になってしまい、どなたか知っている方がいないかと思いまして、質問させて頂いた次第でした。

お礼日時:2010/02/06 20:58

回答ではありません。


目的は、何なのでしょうか?
それによっては、代替案があるかも知れません。
1個だけ位なら、ダミーのテキストボックスを使ってそちらで
取得するとか?
ピクチャーボックスでチェックボックスを作るとか。
少なくても、サブクラス化するよりは簡単かなと思いますよ。
    • good
    • 0
この回答へのお礼

早速のご返信、ありがとうございます。

そうですね、私もピクチャボックスでチェックボックスを作るなどの代替案を考えてみて、幾つか対応できる方法を見つけることができました。

しかし、どうしてサブクラス化をしているのに、方向キーでのWM_KEYDOWNが検知できないのかが分からず、誰か知っている人がいれば教えて頂きたいなと思いまして質問させて頂いた次第です。

お礼日時:2010/02/06 21:14

ハッキリした目的が解らないので、使えるかどうか、解りませんが


一応下記のようにすれば、矢印キーが押された事を取得できます。

Option Explicit

Private Declare Function GetKeyState Lib "user32" _
(ByVal nVirtKey As Long) As Integer
Private Sub Check1_GotFocus()
Label1.Caption = ""
End Sub

Private Sub Check1_LostFocus()
If GetKeyState(vbKeyUp) < 0 Then
Label1.Caption = "↑"
ElseIf GetKeyState(vbKeyDown) < 0 Then
Label1.Caption = "↓"
End If
End Sub
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

あ、私も昨日ググって調べているときに、この方法を目にしました。

確かにこの方法を使えば矢印キーを取得することができるのですが、フォーカス喪失時というのが私がやろうとしていた目的に合わず、いまはサブクラス化でできないか調査している次第でした。

きちんと目的を記載せずに、すいませんでした。

お礼日時:2010/02/06 21:39

>サブクラス化でできないか調査している次第でした。


If uMsg = &H100E Then
If wParam = 3 Then
Debug.Print "↑"
End If
If wParam = 7 Then
Debug.Print "↓"
End If
End If
今試して見たら、uMsg = &H100E で wParam = 3 で ↑ で
wParam = 7 で ↓ が取得できるようです。
理由とかは、現在調査中、取りあえず取得出来る事だけを。
    • good
    • 0

回答番号:No.6 の回答は取消させて下さい。


どうも違うメッセージを拾ってしまったようです。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

あ、そうでしたか。
わざわざ調査して頂きすいません、凄い助かります。

私の方でいま調査していたところ、CreateWindowExを使用し、作成したチェックボックスをサブクラス化した場合は、問題なく矢印キーを検知できることが分かりました。

確かに、VB6のチェックボックスは矢印キーでのフォーカス移動が最初から実装されているので、なんか怪しいとは感じていたのですが。

このことから、VB6のチェックボックス自体がフックなどを使用し、矢印キーの制御を実装しているかもしれませんね。

試しにいまCreateWindowExで作成したチェックボックスにキーボードフックのインストールとサブクラス化を行い、試したみたところ、VB6のコントロールと同様の動きになりました。

いま私が考えられるサブクラス化しても矢印キーを検知できなかった原因としては、これが精一杯ですね。

お礼日時:2010/02/07 11:18

APIで自力でチェックボックスを作成すると、KeyDownが取得できます。


その結果からVB製のチェックボックスは、VBのライブラリ中で、自前サブクラス関数より先にメッセージを捕まえ、カーソルキーであればフォーカス制御の処理を行って、メッセージを破棄していることが予想できます。
破棄されたメッセージは、捕まえようがありません。

なのでVBより先にメッセージを得るために、フックしてあげないとできないのではないかな?


下に、APIでCheckBoxを作成する方法を載せておきます。
値の変更処理とかを追加しないと、OnOffが切り替わりません。
質問者さんのサンプルに、コードの変更と追加です。


モジュール追加で以下
Option Explicit

Private Declare Function CreateWindowEx Lib "user32" Alias "CreateWindowExA" (ByVal dwExStyle As Long, ByVal lpClassName As String, ByVal lpWindowName As String, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As Long, ByVal hMenu As Long, ByVal HINSTANCE As Long, lpParam As Any) As Long
Private Declare Function DestroyWindow Lib "user32" (ByVal hWnd As Long) As Long

Private Const BS_CHECKBOX = &H2&
Private Const WS_CHILD = &H40000000
Private Const WS_VISIBLE = &H10000000

Public Sub CreChk(Owner As Form)
  Dim hChk  As Long
  hChk = CreateWindowEx(0, "Button", "チェックボックス", _
      WS_CHILD Or WS_VISIBLE Or BS_CHECKBOX, _
      0, 0, 150, 30, Owner.hWnd, 0&, App.HINSTANCE, 0&)
  Call SubClass(hChk)
End Sub



あとそちらのサンプルの関数「WindowProc」をちょっと改造
Select Case uMsg
  Case WM_KEYDOWN
    Debug.Print Now & "KeyDown : KeyCode( " & wParam & " )"
  Case WM_DESTROY
    Call UnSubClass(hWnd)
End Select


呼び出しはフォームLoadで
Call CreChk(Me)
Unload時は不要
サブクラス関数内で、勝手に破棄
    • good
    • 0
この回答へのお礼

回答ありがとうございます。

あ、やばい、かぶちゃった…
すいません、先に回答して頂いてたのですね。

分かりやすい回答に、さらにサンプルまで付けて頂き、ありがとうございます。

WindowProcでWM_DESTROY拾って破棄など、とても参考になりました。

お礼日時:2010/02/07 11:35

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!