VBAでキー入力を監視する ― Win32 API GetAsyncKeyStateを32bit/64bit Officeで使う

VBA・Officeカテゴリを表すパンダのイラスト VBA・Office

この記事について
VBAからWin32 APIの GetAsyncKeyState を呼び出し、ExcelやAccessで特定のキー入力を監視する方法を整理します。MicrosoftのWin32 APIおよびVBAの64bit対応に関する公式資料を確認し、既存コードについてもあわせて再レビューしています。

検証ステータス:📘 Microsoft公式仕様確認済み・既存コード再レビュー済み
掲載コードはWindows版OfficeのVBAを想定しています。32bit/64bit OfficeのAPI宣言を考慮していますが、利用するOfficeのバージョンやセキュリティ設定によって動作が異なる場合があります。実際の業務ファイルへ組み込む前に、テスト用ファイルで確認してください。

ExcelやAccessでVBAを使っていると、「特定のキーが押されているか」を通常のイベントとは別に確認したくなることがあります。

たとえば、

  • Esc キーで長時間処理を中断する
  • F12 を押したら特定の処理を実行する
  • Ctrl + Shift + F12 のような複数キーの状態を確認する
  • Accessフォームのフォーカスだけに依存せずキー状態を確認する
  • Windows APIをVBAから呼び出す仕組みを学ぶ

といった用途です。

VBAにはフォームやワークシートのイベントがありますが、Windowsそのものが管理している現在のキー状態を確認したい場合には、Win32 APIの GetAsyncKeyState を利用できます。

この記事では、単にAPIを呼び出すだけではなく、

  1. GetAsyncKeyState が何を返しているのか
  2. 32bit/64bit Officeでの宣言方法
  3. 仮想キーコードの考え方
  4. Excelでのログ記録
  5. Accessフォームでの利用
  6. キーを押しっぱなしにした場合の重複対策
  7. DoEvents と待機処理
  8. 性能・セキュリティ上の注意点

まで順番に確認します。


  1. まず結論:GetAsyncKeyStateで現在のキー状態を確認できる
  2. 1. 背景:なぜVBAからWin32 APIを使うのか
    1. 1.1 VBAにもキーイベントはある
    2. 1.2 GetAsyncKeyStateは「イベント登録」ではない
  3. 2. GetAsyncKeyStateの仕組み
    1. 2.1 最上位ビット:現在押されているか
    2. 2.2 最下位ビットには依存しない
  4. 3. 32bit/64bit OfficeとDeclare PtrSafe
    1. 3.1 VBA7ではPtrSafeを付ける
    2. 3.2 GetAsyncKeyStateではLongPtrは不要
  5. 4. 処理フローを図で確認する
  6. 5. VBAとWindowsのやり取り
  7. 6. 共通モジュール modWinAPI
  8. 7. 仮想キーコードとは
    1. 7.1 左右のShift/Ctrl/Altを区別する場合
  9. 8. Excelでキー入力を監視する
    1. 8.1 押しっぱなしをそのまま記録しない
    2. 8.2 Excel実装例
    3. 8.3 なぜセルへ毎回書かないのか
    4. 8.4 ScreenUpdatingを無条件でOFFにしない理由
  10. 9. Ctrl + Shift + F12のような組み合わせを検出する
  11. 10. ExcelだけならApplication.OnKeyも検討する
  12. 11. Accessでキー状態を監視する
    1. 11.1 フォームの準備
    2. 11.2 Access用モジュール
    3. 11.3 フォームの開始ボタン
  13. 12. DoEventsは何をしているのか
    1. 12.1 DoEventsは非同期処理ではない
    2. 12.2 再入にも注意する
  14. 13. Sleepを入れる理由
  15. 14. 性能を確認する
    1. 14.1 CPU使用率
    2. 14.2 Excelへの書き込み回数
    3. 14.3 UI応答性
  16. 15. 機能検証
    1. 15.1 単独キー
    2. 15.2 押しっぱなし
    3. 15.3 同時押し
    4. 15.4 Escによる終了
  17. 16. 実行手順
    1. Excelの場合
    2. Accessの場合
  18. 17. ロールバック方法
    1. Excel
    2. Access
    3. 監視が止まらない場合
  19. 18. セキュリティとプライバシー
    1. 18.1 必要なキーだけ監視する
    2. 18.2 パスワード等を記録しない
    3. 18.3 GetAsyncKeyStateはキーボードフックとは異なる
  20. 19. 権限・デスクトップ境界と戻り値0について
  21. 20. GetKeyStateとの違い
    1. GetAsyncKeyState
    2. GetKeyState
  22. 21. このサンプルから分かるWindowsの仕組み
  23. 22. 落とし穴と注意点
    1. 22.1 最下位ビットを「1回押された判定」に使わない
    2. 22.2 キーを押しっぱなしにすると毎回ONになる
    3. 22.3 待機なしの無限ループにしない
    4. 22.4 DoEventsを万能な非同期処理として使わない
    5. 22.5 特殊キーは実機確認する
    6. 22.6 API宣言をコピーするときは型まで確認する
  24. 23. どんな用途ならGetAsyncKeyStateが向いているか
  25. 24. 関連する基礎記事
    1. Win32 APIの32bit/64bit対応
    2. DoEventsとイベント処理
  26. 25. 公式情報・一次情報
    1. GetAsyncKeyState
    2. Virtual-Key Codes
    3. 64bit VBA
    4. DoEvents
  27. 26. この次に試してみたいこと
  28. 27. まとめ

まず結論:GetAsyncKeyStateで現在のキー状態を確認できる

もっとも小さい例は次のようになります。

Option Explicit

#If VBA7 Then
    Private Declare PtrSafe Function GetAsyncKeyState _
        Lib "user32" (ByVal vKey As Long) As Integer
#Else
    Private Declare Function GetAsyncKeyState _
        Lib "user32" (ByVal vKey As Long) As Integer
#End If

Private Const VK_ESCAPE As Long = &H1B

Public Sub CheckEscapeKey()

    If GetAsyncKeyState(VK_ESCAPE) < 0 Then
        Debug.Print "Escキーが押されています"
    Else
        Debug.Print "Escキーは押されていません"
    End If

End Sub

GetAsyncKeyState の戻り値はWindows側では SHORT 型です。

その最上位ビットが1になっている場合、そのキーは現在押されています。

VBAの Integer は符号付き16bit整数なので、最上位ビットが立つと戻り値が負数になります。

そのため、

GetAsyncKeyState(VK_ESCAPE) < 0

という判定で、「現在Escキーが押されているか」を確認できます。


1. 背景:なぜVBAからWin32 APIを使うのか

1.1 VBAにもキーイベントはある

ExcelやAccessにもキー入力を扱う仕組みはあります。

たとえばAccessフォームなら KeyDownKeyPress、Excelなら Application.OnKey などを利用できます。

これらは非常に便利ですが、基本的にはOfficeアプリケーション側のイベント処理です。

一方で、

「いまWindowsから見てCtrlキーが押されているか?」

のように、現在のキー状態そのものを問い合わせたい場合があります。

そこで利用できるのが GetAsyncKeyState です。

1.2 GetAsyncKeyStateは「イベント登録」ではない

ここは重要です。

GetAsyncKeyState は、Windowsへ

「このキーはいま押されていますか?」

と問い合わせるAPIです。

キーが押されたら自動的にVBAへ通知されるイベント機構ではありません。

そのため、連続監視したければVBA側から一定間隔で繰り返し問い合わせます。

VBA
 ↓
GetAsyncKeyStateを呼ぶ
 ↓
Windowsが現在のキー状態を返す
 ↓
VBAで判定
 ↓
少し待つ
 ↓
再び問い合わせる

この方式を一般にポーリングと呼びます。


2. GetAsyncKeyStateの仕組み

Windows側の関数定義は概念的には次の形です。

SHORT GetAsyncKeyState(
    int vKey
);

vKey に仮想キーコードを渡し、16bitの値を受け取ります。

2.1 最上位ビット:現在押されているか

現在キーが押されている場合、戻り値の最上位ビットがセットされます。

VBAでは次のどちらでも確認できます。

If GetAsyncKeyState(VK_ESCAPE) < 0 Then

または、

If (GetAsyncKeyState(VK_ESCAPE) And &H8000) <> 0 Then

この記事では、意味が分かりやすい場面では < 0 を使用します。

2.2 最下位ビットには依存しない

以前からあるサンプルコードでは、次のような判定を見かけることがあります。

If (GetAsyncKeyState(VK_A) And &H1) <> 0 Then

これは戻り値の最下位ビットを確認しています。

しかしMicrosoftの現在の公式仕様では、この最下位ビットは16bit Windows時代との互換性のために残っているもので、現在のWindowsでは信頼すべきではないとされています。

別のプログラムが GetAsyncKeyState を呼び出すなど、処理のタイミングによって期待した結果にならない可能性があります。

したがって本記事の更新版では、

  • 現在の押下状態 → 最上位ビットで判定
  • 「押された瞬間」→ VBA側で前回状態を記憶

という方式を使用します。


3. 32bit/64bit OfficeとDeclare PtrSafe

3.1 VBA7ではPtrSafeを付ける

64bit版OfficeからWin32 APIを呼び出す場合、APIの Declare 文には PtrSafe が必要です。

32bit/64bit双方を考慮する場合は、一般的に次のように条件付きコンパイルを使用します。

#If VBA7 Then

    Private Declare PtrSafe Function GetAsyncKeyState _
        Lib "user32" (ByVal vKey As Long) As Integer

#Else

    Private Declare Function GetAsyncKeyState _
        Lib "user32" (ByVal vKey As Long) As Integer

#End If

VBA7 はOffice 2010以降で使用されるVBA環境を判定する条件付きコンパイル定数です。

3.2 GetAsyncKeyStateではLongPtrは不要

64bit対応の記事では LongPtr という型もよく登場します。

しかし、

64bit OfficeならすべてLongPtrにすればよい

という意味ではありません。

LongPtr が必要なのは、主にWindowsのポインターやハンドルを保持する値です。

GetAsyncKeyState の引数はWindowsの int、戻り値は SHORT です。

したがって、

ByVal vKey As Long

As Integer

で対応できます。

これはVBAからWindows APIを扱うときに重要な考え方です。

APIの本来の型を確認して、それに対応するVBA型を選ぶようにします。


4. 処理フローを図で確認する

キー監視全体の流れは次のようになります。

graph TD
    A["開始"] --> B{"監視開始のトリガー"};
    B -- マクロ実行 --> C["監視ループ開始"];
    C --> D["GetAsyncKeyStateを呼び出す"];
    D -- キー状態取得 --> E{"対象キーは現在押されている?"};
    E -- No --> F["前回状態をOFFへ更新"];
    E -- Yes --> G{"前回も押されていた?"};
    G -- Yes --> H["押しっぱなしなので重複処理しない"];
    G -- No --> I["押下としてアクション実行"];
    I --> J["ログ記録・関連処理"];
    F --> K{"Escまたは停止条件を満たした?"};
    H --> K;
    J --> K;
    K -- No --> L["DoEventsを呼び出す"];
    L --> M["Sleepで短時間待機"];
    M --> C;
    K -- Yes --> N["監視ループ終了"];
    N --> O["残りのログを書き出す"];
    O --> P["状態を復元して終了"];

ポイントは、GetAsyncKeyState 自体が「1回だけ押されたこと」を保証しているわけではないことです。

たとえばF12を0.5秒間押したままにすると、20ミリ秒間隔の監視では何回も「F12は押されている」という結果が返ります。

そこでVBA側で前回状態を保持し、

  • 前回OFF → 今回ON:押された瞬間
  • 前回ON → 今回ON:押しっぱなし
  • 前回ON → 今回OFF:キーを離した

と判断します。


5. VBAとWindowsのやり取り

もう少しWindows寄りに見ると、次のような動きです。

graph LR
    A["ユーザーがF12キーを押す"] --> B["Windowsがキー状態を管理"];
    C["VBA"] --> D["GetAsyncKeyState(VK_F12)を呼び出す"];
    D --> B;
    B --> E["SHORT型のキー状態を返す"];
    E --> F["VBAが最上位ビットを判定"];
    F --> G["前回状態と比較"];
    G --> H["必要なVBA処理を実行"];

VBAがキーボードを直接読んでいるわけではありません。

Windowsが管理している状態を、VBAからAPI経由で取得しています。

この構造を理解すると、Win32 APIの仕組みそのものも分かりやすくなります。


6. 共通モジュール modWinAPI

まず、ExcelとAccessの両方から利用できる共通モジュールを作ります。

標準モジュールを追加して、名前を modWinAPI とします。

Option Explicit

' ==============================================================================
' Win32 API
' ==============================================================================

#If VBA7 Then

    ' 指定された仮想キーの現在状態を取得
    Private Declare PtrSafe Function GetAsyncKeyState _
        Lib "user32" (ByVal vKey As Long) As Integer

    ' 監視ループのCPU負荷を抑えるための待機
    Public Declare PtrSafe Sub Sleep _
        Lib "kernel32" (ByVal dwMilliseconds As Long)

#Else

    Private Declare Function GetAsyncKeyState _
        Lib "user32" (ByVal vKey As Long) As Integer

    Public Declare Sub Sleep _
        Lib "kernel32" (ByVal dwMilliseconds As Long)

#End If

' ==============================================================================
' キー状態判定
' ==============================================================================

Public Function IsKeyDown(ByVal vKey As Long) As Boolean

    ' GetAsyncKeyStateの戻り値が負なら、
    ' 最上位ビットがセットされている=現在キーが押されている。
    IsKeyDown = (GetAsyncKeyState(vKey) < 0)

End Function

' ==============================================================================
' よく使う仮想キーコード
' ==============================================================================

Public Const VK_LBUTTON As Long = &H1
Public Const VK_RBUTTON As Long = &H2
Public Const VK_CANCEL As Long = &H3
Public Const VK_MBUTTON As Long = &H4

Public Const VK_BACK As Long = &H8
Public Const VK_TAB As Long = &H9
Public Const VK_CLEAR As Long = &HC
Public Const VK_RETURN As Long = &HD

Public Const VK_SHIFT As Long = &H10
Public Const VK_CONTROL As Long = &H11
Public Const VK_MENU As Long = &H12
Public Const VK_PAUSE As Long = &H13
Public Const VK_CAPITAL As Long = &H14
Public Const VK_ESCAPE As Long = &H1B
Public Const VK_SPACE As Long = &H20

Public Const VK_PRIOR As Long = &H21
Public Const VK_NEXT As Long = &H22
Public Const VK_END As Long = &H23
Public Const VK_HOME As Long = &H24
Public Const VK_LEFT As Long = &H25
Public Const VK_UP As Long = &H26
Public Const VK_RIGHT As Long = &H27
Public Const VK_DOWN As Long = &H28
Public Const VK_SELECT As Long = &H29
Public Const VK_PRINT As Long = &H2A
Public Const VK_EXECUTE As Long = &H2B
Public Const VK_SNAPSHOT As Long = &H2C
Public Const VK_INSERT As Long = &H2D
Public Const VK_DELETE As Long = &H2E
Public Const VK_HELP As Long = &H2F

' 0 ~ 9
Public Const VK_0 As Long = &H30
Public Const VK_1 As Long = &H31
Public Const VK_2 As Long = &H32
Public Const VK_3 As Long = &H33
Public Const VK_4 As Long = &H34
Public Const VK_5 As Long = &H35
Public Const VK_6 As Long = &H36
Public Const VK_7 As Long = &H37
Public Const VK_8 As Long = &H38
Public Const VK_9 As Long = &H39

' A ~ Z
Public Const VK_A As Long = &H41
Public Const VK_B As Long = &H42
Public Const VK_C As Long = &H43
Public Const VK_D As Long = &H44
Public Const VK_E As Long = &H45
Public Const VK_F As Long = &H46
Public Const VK_G As Long = &H47
Public Const VK_H As Long = &H48
Public Const VK_I As Long = &H49
Public Const VK_J As Long = &H4A
Public Const VK_K As Long = &H4B
Public Const VK_L As Long = &H4C
Public Const VK_M As Long = &H4D
Public Const VK_N As Long = &H4E
Public Const VK_O As Long = &H4F
Public Const VK_P As Long = &H50
Public Const VK_Q As Long = &H51
Public Const VK_R As Long = &H52
Public Const VK_S As Long = &H53
Public Const VK_T As Long = &H54
Public Const VK_U As Long = &H55
Public Const VK_V As Long = &H56
Public Const VK_W As Long = &H57
Public Const VK_X As Long = &H58
Public Const VK_Y As Long = &H59
Public Const VK_Z As Long = &H5A

Public Const VK_LWIN As Long = &H5B
Public Const VK_RWIN As Long = &H5C
Public Const VK_APPS As Long = &H5D

' テンキー
Public Const VK_NUMPAD0 As Long = &H60
Public Const VK_NUMPAD1 As Long = &H61
Public Const VK_NUMPAD2 As Long = &H62
Public Const VK_NUMPAD3 As Long = &H63
Public Const VK_NUMPAD4 As Long = &H64
Public Const VK_NUMPAD5 As Long = &H65
Public Const VK_NUMPAD6 As Long = &H66
Public Const VK_NUMPAD7 As Long = &H67
Public Const VK_NUMPAD8 As Long = &H68
Public Const VK_NUMPAD9 As Long = &H69
Public Const VK_MULTIPLY As Long = &H6A
Public Const VK_ADD As Long = &H6B
Public Const VK_SEPARATOR As Long = &H6C
Public Const VK_SUBTRACT As Long = &H6D
Public Const VK_DECIMAL As Long = &H6E
Public Const VK_DIVIDE As Long = &H6F

' F1 ~ F12
Public Const VK_F1 As Long = &H70
Public Const VK_F2 As Long = &H71
Public Const VK_F3 As Long = &H72
Public Const VK_F4 As Long = &H73
Public Const VK_F5 As Long = &H74
Public Const VK_F6 As Long = &H75
Public Const VK_F7 As Long = &H76
Public Const VK_F8 As Long = &H77
Public Const VK_F9 As Long = &H78
Public Const VK_F10 As Long = &H79
Public Const VK_F11 As Long = &H7A
Public Const VK_F12 As Long = &H7B

Public Const VK_NUMLOCK As Long = &H90
Public Const VK_SCROLL As Long = &H91

' OEM系
Public Const VK_OEM_1 As Long = &HBA
Public Const VK_OEM_PLUS As Long = &HBB
Public Const VK_OEM_COMMA As Long = &HBC
Public Const VK_OEM_MINUS As Long = &HBD
Public Const VK_OEM_PERIOD As Long = &HBE
Public Const VK_OEM_2 As Long = &HBF
Public Const VK_OEM_3 As Long = &HC0
Public Const VK_OEM_4 As Long = &HDB
Public Const VK_OEM_5 As Long = &HDC
Public Const VK_OEM_6 As Long = &HDD
Public Const VK_OEM_7 As Long = &HDE

以前のコードにあった、

Public Const ConstVK_L As Long = &H4C

のような定数名は、今回の再レビューで

Public Const VK_L As Long = &H4C

へ修正しています。


7. 仮想キーコードとは

GetAsyncKeyState"A""Esc" と文字列を渡すわけではありません。

Windowsでは、それぞれのキーに仮想キーコード(Virtual-Key Code)が割り当てられています。

代表的なものを表にすると次のようになります。

キー VBA定数
Backspace VK_BACK &H08
Tab VK_TAB &H09
Enter VK_RETURN &H0D
Shift VK_SHIFT &H10
Ctrl VK_CONTROL &H11
Alt VK_MENU &H12
Esc VK_ESCAPE &H1B
Space VK_SPACE &H20
VK_LEFT &H25
VK_UP &H26
VK_RIGHT &H27
VK_DOWN &H28
Insert VK_INSERT &H2D
Delete VK_DELETE &H2E
A VK_A &H41
L VK_L &H4C
Z VK_Z &H5A
Windows VK_LWIN / VK_RWIN &H5B / &H5C
F1 VK_F1 &H70
F12 VK_F12 &H7B
NumLock VK_NUMLOCK &H90
ScrollLock VK_SCROLL &H91

すべての定数を覚える必要はありません。

実際のプログラムでは、監視したいキーだけを定義して使う方が読みやすくなります。

また、日本語キーボードやIME、OEMキーなどでは、物理的な印字とWindowsの仮想キーコードの関係が単純ではない場合があります。

特殊キーを利用するときは、実機で確認するのが安全です。

7.1 左右のShift/Ctrl/Altを区別する場合

VK_SHIFTVK_CONTROLVK_MENU は左右をまとめた状態を取得します。

左右を区別したい場合は、Windowsには次の仮想キーコードも用意されています。

キー VBA定数例
左Shift VK_LSHIFT &HA0
右Shift VK_RSHIFT &HA1
左Ctrl VK_LCONTROL &HA2
右Ctrl VK_RCONTROL &HA3
左Alt VK_LMENU &HA4
右Alt VK_RMENU &HA5

必要であれば、modWinAPI の定数定義へこれらを追加して利用できます。


8. Excelでキー入力を監視する

ここからExcelで実際に試します。

例として、

  • A
  • Z
  • Ctrl
  • Enter

を監視し、

  • キー
  • 仮想キーコード
  • 時刻

をSheet1へ記録します。

Esc が押されたら監視を終了します。

8.1 押しっぱなしをそのまま記録しない

単純に次のようにすると、

If IsKeyDown(VK_A) Then
    Debug.Print "A"
End If

Aキーを押しっぱなしにした間、ループするたびにログが増えます。

そこで、

前回 OFF → 今回 ON

となった瞬間だけを記録します。

状態遷移は次のとおりです。

前回 今回 意味 処理
OFF OFF 押されていない なし
OFF ON 押された瞬間 ログ
ON ON 押しっぱなし なし
ON OFF 離された 状態更新

8.2 Excel実装例

標準モジュール modExcelKeyMonitor を作成します。

Option Explicit

Private Const BUFFER_SIZE As Long = 100
Private Const POLLING_INTERVAL_MS As Long = 20

Public IsMonitoring As Boolean

Public Sub StartKeyMonitor_Excel()

    ' DoEvents中の再入を防ぐ。
    ' すでに監視中なら、同じ監視ループを二重起動しない。
    If IsMonitoring Then
        Debug.Print "すでにキー入力監視を実行中です。"
        Exit Sub
    End If

    Dim wasA As Boolean
    Dim wasZ As Boolean
    Dim wasCtrl As Boolean
    Dim wasEnter As Boolean

    Dim nowA As Boolean
    Dim nowZ As Boolean
    Dim nowCtrl As Boolean
    Dim nowEnter As Boolean

    Dim logBuffer() As Variant
    Dim logCount As Long
    Dim nextRow As Long

    Dim ws As Worksheet

    On Error GoTo ErrorHandler

    Set ws = ThisWorkbook.Worksheets("Sheet1")

    ' ログ領域の準備
    ws.Cells(1, 1).Value = "日時"
    ws.Cells(1, 2).Value = "仮想キーコード"
    ws.Cells(1, 3).Value = "キー名"
    ws.Cells(1, 4).Value = "状態"

    nextRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row + 1

    If nextRow < 2 Then
        nextRow = 2
    End If

    ReDim logBuffer(1 To BUFFER_SIZE, 1 To 4)

    logCount = 0
    IsMonitoring = True

    ' 監視開始前から押されているキーを「新規押下」と誤認しないよう、
    ' 初期状態を現在のキー状態でそろえる。
    wasA = IsKeyDown(VK_A)
    wasZ = IsKeyDown(VK_Z)
    wasCtrl = IsKeyDown(VK_CONTROL)
    wasEnter = IsKeyDown(VK_RETURN)

    Debug.Print "キー入力監視を開始しました。"
    Debug.Print "Escキーで終了します。"

    Do While IsMonitoring

        ' Escが押されたら終了
        If IsKeyDown(VK_ESCAPE) Then
            IsMonitoring = False
            Exit Do
        End If

        ' 現在の状態を取得
        nowA = IsKeyDown(VK_A)
        nowZ = IsKeyDown(VK_Z)
        nowCtrl = IsKeyDown(VK_CONTROL)
        nowEnter = IsKeyDown(VK_RETURN)

        ' OFF → ON の瞬間だけログへ追加
        If nowA And Not wasA Then
            AddLogToBuffer _
                VK_A, "A", logBuffer, logCount, nextRow, ws
        End If

        If nowZ And Not wasZ Then
            AddLogToBuffer _
                VK_Z, "Z", logBuffer, logCount, nextRow, ws
        End If

        If nowCtrl And Not wasCtrl Then
            AddLogToBuffer _
                VK_CONTROL, "Ctrl", logBuffer, logCount, nextRow, ws
        End If

        If nowEnter And Not wasEnter Then
            AddLogToBuffer _
                VK_RETURN, "Enter", logBuffer, logCount, nextRow, ws
        End If

        ' 現在状態を次回比較用に保存
        wasA = nowA
        wasZ = nowZ
        wasCtrl = nowCtrl
        wasEnter = nowEnter

        ' Windowsへ処理を返す
        DoEvents

        ' ポーリング間隔を空けてCPU負荷を抑える
        Sleep POLLING_INTERVAL_MS

    Loop

CleanExit:

    ' 終了処理中の二次エラーでエラーハンドラをループさせない。
    On Error Resume Next

    If logCount > 0 And Not ws Is Nothing Then
        FlushLogBuffer logBuffer, logCount, nextRow, ws
    End If

    IsMonitoring = False
    Set ws = Nothing

    On Error GoTo 0

    Debug.Print "キー入力監視を終了しました。"
    Exit Sub

ErrorHandler:

    Debug.Print _
        "エラー " & Err.Number & ": " & Err.Description

    Resume CleanExit

End Sub

Private Sub AddLogToBuffer( _
    ByVal vKey As Long, _
    ByVal keyName As String, _
    ByRef logBuffer() As Variant, _
    ByRef logCount As Long, _
    ByRef nextRow As Long, _
    ByVal ws As Worksheet)

    logCount = logCount + 1

    logBuffer(logCount, 1) = Now
    logBuffer(logCount, 2) = vKey
    logBuffer(logCount, 3) = keyName
    logBuffer(logCount, 4) = "押下"

    If logCount >= UBound(logBuffer, 1) Then
        FlushLogBuffer logBuffer, logCount, nextRow, ws
    End If

End Sub

Private Sub FlushLogBuffer( _
    ByRef logBuffer() As Variant, _
    ByRef logCount As Long, _
    ByRef nextRow As Long, _
    ByVal ws As Worksheet)

    Dim outputData() As Variant
    Dim r As Long
    Dim c As Long

    If logCount <= 0 Then
        Exit Sub
    End If

    ReDim outputData(1 To logCount, 1 To 4)

    For r = 1 To logCount
        For c = 1 To 4
            outputData(r, c) = logBuffer(r, c)
        Next c
    Next r

    ws.Cells(nextRow, 1) _
        .Resize(logCount, 4) _
        .Value = outputData

    nextRow = nextRow + logCount
    logCount = 0

    ReDim logBuffer(1 To BUFFER_SIZE, 1 To 4)

End Sub

Public Sub StopKeyMonitor_Excel()

    IsMonitoring = False

End Sub

8.3 なぜセルへ毎回書かないのか

次のように、キー検出のたびにセルへ書くこともできます。

Cells(Rows.Count, 1).End(xlUp).Offset(1).Value = Now

ただし、イベント数が増えるとExcelオブジェクトへのアクセス回数も増えます。

そこでこの例では、いったん配列へ蓄積してからまとめてシートへ書き込みます。

キー検出
 ↓
VBA配列へ保存
 ↓
100件たまる
 ↓
Excelへまとめて書き込み

大量データを扱うVBAでは、セルを1個ずつ操作するより、配列を介してまとめて読み書きする方法が有効なことがあります。

8.4 ScreenUpdatingを無条件でOFFにしない理由

以前のコードでは、監視開始時に

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False

とする構成も考えられます。

しかし今回の目的はユーザー操作を受け付けながらキー状態を監視することです。

長時間の監視中に画面更新やイベントを止めてしまうと、本来のExcel操作に影響します。

そのため更新版では、監視ループ自体ではこれらを無条件にOFFにしません。

大量のExcel処理も同時に行う場合には、その処理部分だけ別途最適化する方が安全です。


9. Ctrl + Shift + F12のような組み合わせを検出する

GetAsyncKeyState は、それぞれのキーの現在状態を取得できます。

そのため複数キーの組み合わせも比較的簡単です。

Public Sub CheckShortcut()

    If IsKeyDown(VK_CONTROL) _
       And IsKeyDown(VK_SHIFT) _
       And IsKeyDown(VK_F12) Then

        Debug.Print "Ctrl + Shift + F12"

    End If

End Sub

ただし、このコードもループ内で実行すれば押している間は毎回Trueになります。

1回だけ処理したい場合は、組み合わせ自体の前回状態を保持します。

Dim wasShortcut As Boolean
Dim isShortcut As Boolean

isShortcut = _
    IsKeyDown(VK_CONTROL) _
    And IsKeyDown(VK_SHIFT) _
    And IsKeyDown(VK_F12)

If isShortcut And Not wasShortcut Then

    Debug.Print _
        "Ctrl + Shift + F12 が押されました"

End If

wasShortcut = isShortcut

10. ExcelだけならApplication.OnKeyも検討する

Excel内のショートカットを作りたいだけなら、必ずしもWin32 APIを使う必要はありません。

Excelには Application.OnKey があります。

たとえば、

Application.OnKey "^+{F12}", "MyMacro"

のようにExcel内のショートカットを設定できます。

大まかな使い分けは次のようになります。

方法 向いている用途
Application.OnKey Excel内で特定ショートカットを割り当てる
KeyDown / KeyPress フォームやコントロール上のキーイベント
GetAsyncKeyState Windowsが管理する現在のキー状態を確認
キーボードフック より高度なイベント監視

「ExcelでCtrl+Shift+F12を押したらマクロを実行する」だけなら Application.OnKey の方がシンプルです。

一方、

長い処理中にEscが押されているか定期的に確認したい

といった用途では GetAsyncKeyState が分かりやすい場合があります。


11. Accessでキー状態を監視する

Accessでも同じ modWinAPI を利用できます。

ここではフォーム上のテキストボックスへログを追加する例を作ります。

11.1 フォームの準備

例として次のようにします。

  • フォーム名:Form1
  • テキストボックス:txtLog
  • 開始ボタン:cmdStartMonitor

txtLog は複数行表示できるようにしておきます。

11.2 Access用モジュール

標準モジュール modAccessKeyMonitor を作成します。

Option Explicit

Private Const POLLING_INTERVAL_MS As Long = 20

Public IsAccessMonitoring As Boolean

Private AccessFormName As String
Private AccessControlName As String

Public Sub StartKeyMonitor_Access( _
    ByVal FormName As String, _
    ByVal ControlName As String)

    ' DoEvents中の再入を防ぐ。
    If IsAccessMonitoring Then
        MsgBox "すでにキー入力監視を実行中です。", vbInformation
        Exit Sub
    End If

    Dim frm As Access.Form
    Dim ctl As Access.TextBox

    Dim wasA As Boolean
    Dim wasEnter As Boolean
    Dim wasShift As Boolean
    Dim wasCtrl As Boolean

    Dim nowA As Boolean
    Dim nowEnter As Boolean
    Dim nowShift As Boolean
    Dim nowCtrl As Boolean

    On Error GoTo ErrorHandler

    Set frm = Forms(FormName)
    Set ctl = frm.Controls(ControlName)

    AccessFormName = FormName
    AccessControlName = ControlName

    ctl.Value = _
        "キー入力監視を開始しました。" & vbCrLf & _
        "Escキーで終了します。" & vbCrLf & vbCrLf

    IsAccessMonitoring = True

    ' 監視開始前から押されているキーを新規押下と誤認しない。
    wasA = IsKeyDown(VK_A)
    wasEnter = IsKeyDown(VK_RETURN)
    wasShift = IsKeyDown(VK_SHIFT)
    wasCtrl = IsKeyDown(VK_CONTROL)

    Do While IsAccessMonitoring

        If IsKeyDown(VK_ESCAPE) Then
            IsAccessMonitoring = False
            Exit Do
        End If

        nowA = IsKeyDown(VK_A)
        nowEnter = IsKeyDown(VK_RETURN)
        nowShift = IsKeyDown(VK_SHIFT)
        nowCtrl = IsKeyDown(VK_CONTROL)

        If nowA And Not wasA Then
            AppendAccessLog VK_A, "A"
        End If

        If nowEnter And Not wasEnter Then
            AppendAccessLog VK_RETURN, "Enter"
        End If

        If nowShift And Not wasShift Then
            AppendAccessLog VK_SHIFT, "Shift"
        End If

        If nowCtrl And Not wasCtrl Then
            AppendAccessLog VK_CONTROL, "Ctrl"
        End If

        wasA = nowA
        wasEnter = nowEnter
        wasShift = nowShift
        wasCtrl = nowCtrl

        DoEvents
        Sleep POLLING_INTERVAL_MS

    Loop

CleanExit:

    On Error Resume Next

    ctl.Value = _
        Nz(ctl.Value, "") & vbCrLf & _
        "キー入力監視を終了しました。"

    ' SelStartはフォーカスがないTextBoxではエラーになる場合があるため、
    ' このサンプルでは自動スクロール目的のSelStart操作を行わない。

    Set ctl = Nothing
    Set frm = Nothing

    IsAccessMonitoring = False

    Exit Sub

ErrorHandler:

    MsgBox _
        "キー入力監視でエラーが発生しました。" & vbCrLf & _
        Err.Number & ": " & Err.Description, _
        vbExclamation

    Resume CleanExit

End Sub

Private Sub AppendAccessLog( _
    ByVal vKey As Long, _
    ByVal keyName As String)

    Dim frm As Access.Form
    Dim ctl As Access.TextBox
    Dim logEntry As String

    Set frm = Forms(AccessFormName)
    Set ctl = frm.Controls(AccessControlName)

    logEntry = _
        Format$(Now, "yyyy/mm/dd hh:nn:ss") & _
        "  VK=" & CStr(vKey) & _
        "  " & keyName

    ctl.Value = _
        Nz(ctl.Value, "") & _
        logEntry & vbCrLf

    ' ログ表示のためにフォーカスを強制移動すると操作性へ影響するため、
    ' SelStartによる自動スクロールは行わない。

    Set ctl = Nothing
    Set frm = Nothing

End Sub

Public Sub StopKeyMonitor_Access()

    IsAccessMonitoring = False

End Sub

11.3 フォームの開始ボタン

cmdStartMonitor のクリックイベントには次のように記述します。

Private Sub cmdStartMonitor_Click()

    StartKeyMonitor_Access _
        "Form1", _
        "txtLog"

End Sub

実行すると、A・Enter・Shift・Ctrlの押下をテキストボックスへ記録し、Escで終了します。


12. DoEventsは何をしているのか

監視コードには次の1行があります。

DoEvents

VBAで無限に近いループを実行すると、その間VBAが処理を占有してしまい、ExcelやAccessの画面が固まったように見えることがあります。

DoEvents は、一時的にオペレーティングシステムへ制御を戻して、待機中のイベントなどを処理する機会を与えます。

そのため、

Do While IsMonitoring

    ' キー状態確認

    DoEvents

Loop

とすることで、ユーザー操作を受け付ける余地を作れます。

12.1 DoEventsは非同期処理ではない

ここも重要です。

DoEvents を入れたからといって、VBAがマルチスレッドや非同期処理になるわけではありません。

あくまで、いったんWindows側へ処理を渡してから戻ってきています。

VBA処理
 ↓
DoEvents
 ↓
Windowsがイベントを処理
 ↓
VBAへ戻る
 ↓
処理継続

12.2 再入にも注意する

DoEvents 中には別のイベント処理が動く可能性があります。

その結果、同じマクロが再び呼び出される設計になっていると、処理が二重に動く可能性があります。

実務で使う場合は、

  • 同じ開始ボタンを連打できないようにする
  • 実行中はボタンを無効化する
  • 実行中フラグを持つ
  • 終了処理を必ず通す

といった対策も検討します。


13. Sleepを入れる理由

次のように待機なしでループすると、

Do

    If IsKeyDown(VK_F12) Then
        ' 処理
    End If

    DoEvents

Loop

VBAは可能な限り高速にループを繰り返します。

キー状態を1秒に数万回確認しても、通常の人間のキー操作を検出する目的ではほとんど意味がありません。

そこで、

Sleep 20

のように短い待機を入れます。

20ミリ秒なら、理論上は最大でおおむね1秒50回程度のポーリングです。

1000 ms ÷ 20 ms ≒ 50回/秒

一般的なショートカットや停止キーの検出なら、かなり細かい間隔です。


14. 性能を確認する

キー監視は短いコードですが、無限ループに近い処理です。

実装後は性能面も確認します。

14.1 CPU使用率

WindowsのタスクマネージャーでExcelまたはAccessのCPU使用率を確認します。

待機なしでループしている場合、CPU使用率が不必要に上昇する可能性があります。

その場合は、

Sleep 20

を、

Sleep 30

または、

Sleep 50

へ変更して比較します。

反対に、待機を長くしすぎれば入力検出が遅れます。

用途に合わせて調整します。

14.2 Excelへの書き込み回数

キー検出のたびに、

Cells(...).Value = ...

を実行すると、Excelオブジェクトモデルへのアクセスが増えます。

今回のExcel例では100件単位のバッファを使っています。

キーイベント
 ↓
配列
 ↓
配列
 ↓
配列
 ↓
一定数蓄積
 ↓
Rangeへまとめて出力

このようにVBA内部の処理とExcelシートへのアクセスを分離すると、コードの構造も分かりやすくなります。

14.3 UI応答性

監視中に、

  • Excelのセルを選択できるか
  • シートをスクロールできるか
  • Accessフォームを操作できるか
  • Escで監視を終了できるか

を確認します。

監視そのものによってOfficeアプリケーションが操作不能になる場合は、ポーリング間隔や設計を見直します。


15. 機能検証

実装後は少なくとも次を確認します。

15.1 単独キー

  • A
  • Z
  • Enter
  • Esc
  • F12

などを1つずつ確認します。

15.2 押しっぱなし

Aキーを1秒程度押したままにします。

今回のコードでは、

A
A
A
A
A

と大量に記録されるのではなく、基本的には

A

と1回だけ記録されることを確認します。

一度キーを離して再度押せば、再び1件記録されます。

15.3 同時押し

Ctrlを押しながらAを押すなど、複数キーを同時に押します。

各キーを個別に取得しているので、

Ctrl
A

のようにそれぞれ確認できます。

ショートカットとして扱う場合は、先ほどのように複数の IsKeyDown をAND条件にします。

15.4 Escによる終了

監視中にEscを押し、必ずループを終了できることを確認します。

無限ループ型の処理では、

「どう始めるか」より「どう確実に止めるか」

を先に設計しておく方が安全です。


16. 実行手順

Excelの場合

  1. 対象Excelファイルを開く
  2. Alt + F11 でVBEを開く
  3. 「挿入」→「標準モジュール」
  4. modWinAPI を作成
  5. 共通APIコードを貼り付ける
  6. modExcelKeyMonitor を作成
  7. Excel用コードを貼り付ける
  8. Sheet1 が存在することを確認
  9. StartKeyMonitor_Excel を実行
  10. A、Z、Ctrl、Enterなどを押す
  11. Esc で終了
  12. Sheet1のログを確認する

Accessの場合

  1. Accessデータベースを開く
  2. Alt + F11 でVBEを開く
  3. modWinAPI を作成
  4. modAccessKeyMonitor を作成
  5. Form1 を作成
  6. txtLog テキストボックスを作成
  7. cmdStartMonitor ボタンを作成
  8. ボタンのクリックイベントへ開始コードを記述
  9. フォームを開く
  10. ボタンを押して監視開始
  11. キー入力を確認
  12. Esc で終了

17. ロールバック方法

Win32 APIを利用するコードでも、今回の例はWindowsやOfficeの設定そのものを書き換えているわけではありません。

不要になった場合は、VBAモジュールを削除すれば元に戻せます。

Excel

削除対象:

modWinAPI
modExcelKeyMonitor

必要に応じてSheet1のログも削除します。

Access

削除対象:

modWinAPI
modAccessKeyMonitor

さらにサンプル用として作成した、

Form1
txtLog
cmdStartMonitor

なども不要なら削除します。

監視が止まらない場合

まず Esc を試します。

VBEから処理を強制停止する場合は、実行環境に応じてVBEの停止ボタンや Ctrl + Break などを利用します。

テスト時には、必ず保存済みのファイルまたはコピーで試すのが安全です。


18. セキュリティとプライバシー

GetAsyncKeyState は便利ですが、キー入力を扱うAPIなので用途には注意が必要です。

18.1 必要なキーだけ監視する

今回の例では、

Esc
A
Z
Ctrl
Enter
F12

など、目的が明確なキーだけを対象にしています。

通常の業務マクロで、すべての文字キーを無差別に記録する必要はありません。

18.2 パスワード等を記録しない

キーボード入力の監視方法を誤ると、

  • パスワード
  • 個人情報
  • メール本文
  • 認証情報

など、本来収集すべきでない情報まで記録してしまう可能性があります。

本記事のコードは、特定キーによる操作・停止・学習用途を想定しています。

入力内容を収集する仕組みとして使用しないでください。

18.3 GetAsyncKeyStateはキーボードフックとは異なる

GetAsyncKeyState は、現在のキー状態を問い合わせるAPIです。

Windowsへフックを登録し、すべてのキーボードイベントを受信する仕組みとは異なります。

用途に必要以上の仕組みを導入しないことも、安全な設計の一部です。


19. 権限・デスクトップ境界と戻り値0について

通常のデスクトップ上で利用する範囲では、GetAsyncKeyState を呼び出すためだけに管理者権限を要求する必要はありません。

ただし、「Windows上のあらゆるアプリのキー状態を必ず取得できる」と考えるのは適切ではありません。

Microsoft Learnでは、GetAsyncKeyState の呼び出しに失敗した場合は戻り値が 0 になり、その例として次の条件が挙げられています。

  • 現在のデスクトップがアクティブなデスクトップではない
  • UI Privilege Isolation(UIPI)により、呼び出し元スレッドから前景スレッドへのアクセスが制限される
  • 前景スレッドが別プロセスに属し、必要なデスクトップアクセス権を呼び出し元が持っていない

このため、False が返ったときは常に「物理的にキーが押されていない」とだけ断定するのではなく、取得対象・デスクトップ・権限境界によっては取得できていない可能性もあることを理解しておくと安全です。

UACのセキュアデスクトップや、別セッション/別デスクトップをまたいだ監視は、このサンプルが想定する通常のOffice VBA利用とは別の問題として扱います。


20. GetKeyStateとの違い

Windowsには似た名前のAPIがあります。

GetAsyncKeyState

現在のキー状態を問い合わせる

という使い方が中心です。

VBAからポーリングして、

「今Ctrlは押されているか?」

を調べる今回の用途に向いています。

GetKeyState

キー状態を確認するAPIですが、メッセージキューの状態との関係など、GetAsyncKeyState とは意味が異なります。

単に名前が似ているから交換可能というわけではありません。

Windows APIでは、

「似た名前のAPIでも、どのタイミングの状態を返すのか」

を公式仕様で確認することが重要です。


21. このサンプルから分かるWindowsの仕組み

このコードは、単なる「VBAでキーを取るテクニック」だけではありません。

VBAからWindowsを見る教材としても面白い例です。

ユーザー
 ↓
キーボード
 ↓
Windows
 ↓
仮想キーコードとして状態管理
 ↓
Win32 API
 ↓
VBA
 ↓
Excel / Access

Excel VBAだけを書いていると、

Range("A1").Value = "Hello"

のようにExcelオブジェクトモデルの中で処理が完結することが多くなります。

しかしWin32 APIを使うと、

OfficeアプリケーションもWindows上で動いている1つのプロセス

であることが見えてきます。

GetAsyncKeyState は、VBAからWindows APIを学ぶ最初の題材として比較的分かりやすいAPIです。


22. 落とし穴と注意点

22.1 最下位ビットを「1回押された判定」に使わない

古いサンプルで、

GetAsyncKeyState(vKey) And &H1

を使っているケースがあります。

現在のWindowsでは、これを確実なイベント履歴として扱わない方が安全です。

今回のコードでは、前回のON/OFF状態をVBA側で保持しています。

22.2 キーを押しっぱなしにすると毎回ONになる

GetAsyncKeyState は状態を返すので、押している間は繰り返し「押されている」と判定されます。

必要に応じてエッジ検出を入れます。

22.3 待機なしの無限ループにしない

Do
    ' GetAsyncKeyState
Loop

のようなコードはCPUを無駄に使用する可能性があります。

DoEvents と短い待機を組み合わせます。

22.4 DoEventsを万能な非同期処理として使わない

DoEvents は非同期APIでもスレッド生成でもありません。

再入の可能性もあるため、長時間の複雑なバックグラウンド処理には別の設計が必要になる場合があります。

22.5 特殊キーは実機確認する

日本語キーボード、IME、OEMキー、ノートPC固有キーなどでは想定どおりにならない場合があります。

公式のVirtual-Key Code一覧を確認するとともに、実際の環境でも確認します。

22.6 API宣言をコピーするときは型まで確認する

PtrSafe を付けただけで、すべてのWin32 APIが64bit対応になるわけではありません。

ハンドルやポインターを返す別のAPIでは LongPtr が必要になる場合があります。

APIごとにWindows側の型を確認します。


23. どんな用途ならGetAsyncKeyStateが向いているか

向いている例:

  • EscでVBA処理を停止する
  • 特定のファンクションキーを監視する
  • CtrlやShiftの現在状態を確認する
  • Windows APIの学習
  • Office上で簡単な入力状態を確認する

別の方式を検討した方がよい例:

  • Excel内の単純なショートカット登録
  • フォーム内部だけのキーイベント
  • 常駐型の本格的キーボード監視
  • すべてのキー入力内容の収集
  • VBAを閉じても動作し続ける処理

目的によって、Application.OnKey、フォームイベント、Windowsメッセージ、別アプリケーションなどを使い分けます。


24. 関連する基礎記事

今回の記事は GetAsyncKeyState を題材にしていますが、Win32 APIや DoEvents の基礎を先に確認したい場合は、次の記事もあわせて読むと理解しやすくなります。

Win32 APIの32bit/64bit対応

【VBA】Win32 APIを64bit環境で安全に動作させるPtrSafeとLongPtrの完全ガイド

PtrSafeLongPtr の役割、32bit/64bit OfficeでAPI宣言をどう考えるかを整理しています。

今回の GetAsyncKeyState では、引数が int、戻り値が SHORT なので LongPtr へ置き換える場所はありませんが、FindWindowGetForegroundWindow などWindowsハンドルを扱うAPIへ進むと LongPtr が重要になります。

DoEventsとイベント処理

VBAのDoEventsは何をしているのか ― 画面が固まる理由とイベント処理を理解する

今回の監視ループでも使用している DoEvents について、OSへ制御を渡すとはどういう意味か、なぜ非同期処理とは違うのか、再入にどんな注意が必要なのかを整理しています。


25. 公式情報・一次情報

この記事では、Win32 APIやVBAの挙動について、Microsoftの公式ドキュメントを基準に確認しています。

GetAsyncKeyState

Microsoft Learn — GetAsyncKeyState function (winuser.h)

確認できる内容:

  • GetAsyncKeyState の引数・戻り値
  • 最上位ビットによる現在のキー状態
  • 最下位ビットへ依存すべきではない理由
  • キーボード状態取得時の注意点

Virtual-Key Codes

Microsoft Learn — Virtual-Key Codes (Winuser.h)

確認できる内容:

  • VK_ESCAPE
  • VK_CONTROL
  • VK_SHIFT
  • VK_F1VK_F12
  • 数字・英字
  • マウスボタン
  • IME関連キー
  • OEMキー

など、Windowsが使用する仮想キーコードの正式な一覧です。

64bit VBA

Microsoft Learn — 64-bit Visual Basic for Applications overview

確認できる内容:

  • PtrSafe
  • LongPtr
  • LongLong
  • VBA7
  • 32bit/64bit OfficeのDeclare文
  • ポインターやハンドルを扱う場合の型

DoEvents

Microsoft Learn — DoEvents 関数

確認できる内容:

  • OSへ制御を渡す仕組み
  • イベントキューの処理
  • ユーザーによる中断を受け付ける用途
  • 再入に関する注意点

公式仕様は更新される場合があります。

実際に業務ファイルへ組み込む場合は、Microsoft Learnの最新情報もあわせて確認してください。


26. この次に試してみたいこと

GetAsyncKeyState を理解したら、次は次のようなWin32 APIへ進むと、Windowsの仕組みがさらに見えやすくなります。

  1. GetForegroundWindow

    • 現在前面にあるウィンドウのハンドルを取得する
  2. GetWindowText

    • ウィンドウタイトルを取得する
  3. GetCursorPos

    • 現在のマウスカーソル座標を取得する
  4. FindWindow

    • ウィンドウを検索してハンドルを取得する
  5. SendMessage / PostMessage

    • Windowsメッセージを送る仕組みを体験する
  6. Application.OnKey

    • Win32 APIを使わずExcel内だけでショートカットを処理する

こうしたAPIを順番に試していくと、

VBA → Win32 API → Windows → GUI

という関係が見えてきます。


27. まとめ

VBAからWin32 APIの GetAsyncKeyState を利用すると、Windowsが管理している現在のキー状態を確認できます。

今回のポイントは次のとおりです。

  • GetAsyncKeyState は仮想キーコードを受け取る
  • 最上位ビットから現在の押下状態を判断する
  • 最下位ビットを「押された履歴」として信頼しない
  • 押された瞬間はVBA側で前回状態と比較する
  • 64bit Officeでは PtrSafe を考慮する
  • ポインターやハンドルでなければ何でも LongPtr にするわけではない
  • 連続監視では DoEvents と適切な待機時間を考慮する
  • Excelではログを配列へバッファしてまとめて出力できる
  • Accessでも同じAPI宣言を再利用できる
  • 必要なキーだけを対象とし、不要な入力情報を収集しない

特に重要なのは、

GetAsyncKeyStateは「キー入力イベントを受信するAPI」ではなく、「その瞬間のキー状態をWindowsへ問い合わせるAPI」である

という点です。

VBAから少しだけWindows側へ踏み込んでみると、普段使っているExcelやAccessもWindows上でイベントやメッセージを処理しながら動作していることが見えてきます。

このサンプルは、VBAからWin32 APIを学び始める題材としても使いやすい例です。


文書情報

記事タイトル
VBAでキー入力を監視する ― Win32 API GetAsyncKeyStateを32bit/64bit Officeで使う
作成日
更新日
Source URL
https://papanda925.com/?p=3944

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました