t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBA Win32API GetAsyncKeyStateを使ってマクロ実行時に特定キーが押されているかを検知する。

今回はWin32APIのGetAsyncKeyStateを使ってマクロ実行時に特定キーが押されているかを検知するコード。
この手の情報は既に沢山出ているが、検索したサイトはいずれも情報が完全ではなかったので少し苦労した。

では、早速完成コードを紹介する。
今回はシフトキーが押されているかどうかを検知した。

コード

#If Win64 Then
    Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As LongLong) As Integer
#Else
    #If VBA6 Or VBA5 Then
        Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Integer
    #Else
        Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Integer
    #End If
#End If
'GetAsyncKeyStateは、-32768, 1, -32767, 0 のうちいずれかの16ビット整数を返す。
'これは二進数に直したときのビットに意味がある。
'-32768: 1000 0000 0000 0000  最上位ビットが1なら、現在そのキーが押されていることを示す。
'1         : 0000 0000 0000 0001 最下位ビットが1なら、最後のGetAsyncKeyState呼び出しの後にそのキーが押されたことを示す。
'-32767: 1000 0000 0000 0001 従って、これは両方に該当することを示す。
'0         :0000 0000 0000 0000 これは、どちらでもないことを示す。
'つまり現在キーが押されているかどうかを知るには、GetAsyncKeyStateの結果を-32768のAndマスクに掛け、
'-32768になればOKということ。

Function IsShiftKeyPressed() As Boolean
    Const KEY_PRESSED = -32768      '1000 0000 0000 0000 最上位ビットが1であることを示す。
    IsShiftKeyPressed = (GetAsyncKeyState(vbKeyShift) And KEY_PRESSED) = KEY_PRESSED
End Function

Sub hoge()
    Debug.Print IsShiftKeyPressed
End Sub

用途

私が公開しているフローチャート作成ツール BreadChart に機能搭載するのが目的。
Connectorモードのとき、条件分岐するシチュエーションにおいて、新しいコネクタから開始するのにわざわざモードをOff・OnしないといけないのをShift+Clickで出来るようにした。
thom.hateblo.jp

というワケで、昨日1.2を出したところだけど、1.3をしれっと公開済み。

参考サイトと苦労したポイント

最初に検索にヒットしたのがこちら。
officetanaka.net
ただ中身をみてみるとコマンドボタン限定なので今回の用途にはマッチしなかった。

次にこちら。
excel-excel.com
キーコード付きでサンプルが掲載されていてとてもわかりやすい。

そしてWin32APIを使う以上、64bit対応も考慮したかったので、こちらも参考にした。
ameblo.jp

それで単純なサンプルを書いてみた。

#If Win64 Then
    Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As LongLong) As Long
#Else
    #If VBA6 Or VBA5 Then
        Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
    #Else
        Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
    #End If
#End If

Sub hoge()
    Debug.Print IIf(GetAsyncKeyState(vbKeyControl), "Pressed", "Not Pressed")
End Sub

しかし、なぜかCtrlを押してない時までPressedになることがあり、次のコードで値を見てみた。

Sub hoge()
    Debug.Print GetAsyncKeyState(vbKeyControl)
End Sub

結果は、Ctrlが押されているときが32768か-32767、押されていない時が1か0になる。
なんじゃこれと思って調べてみると、以下のサイトに解説があった。
tokovalue.jp

この関数は、最上位ビットと最下位ビットに意味があるのだが、そもそもこれ、戻り値はInteger(16ビット整数)だそうだ。
最上位ビットは符号を表すので、IntegerとLongでは解釈が異なってしまう。

ということで修正。

#If Win64 Then
    Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As LongLong) As Integer
#Else
    #If VBA6 Or VBA5 Then
        Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Integer
    #Else
        Declare PtrSafe Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Integer
    #End If
#End If

これで戻り値はCtrlが押されているときが-32768か-32767、押されていない時が1か0になった。

Ctrlが押されているとき、

  • 32768は1000 0000 0000 0000
  • 32767は1000 0000 0000 0001

Ctrlが押されていないとき、
1は0000 0000 0000 0001
0は0000 0000 0000 0000

つまり、最上位ビットが1かどうかを知りたければ、戻り値と-32768をAndしてやれば良い。
それで完成したのが冒頭のコードだ。

なんでShiftに変えたかというと、BreadChartに組み込む際に、Ctrl+クリックではマクロが発動する以前にオートシェイプの選択として機能してしまい、使えなかったので。

以上。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。