この記事について
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を呼び出すだけではなく、
GetAsyncKeyStateが何を返しているのか- 32bit/64bit Officeでの宣言方法
- 仮想キーコードの考え方
- Excelでのログ記録
- Accessフォームでの利用
- キーを押しっぱなしにした場合の重複対策
DoEventsと待機処理- 性能・セキュリティ上の注意点
まで順番に確認します。
- まず結論:GetAsyncKeyStateで現在のキー状態を確認できる
- 1. 背景:なぜVBAからWin32 APIを使うのか
- 2. GetAsyncKeyStateの仕組み
- 3. 32bit/64bit OfficeとDeclare PtrSafe
- 4. 処理フローを図で確認する
- 5. VBAとWindowsのやり取り
- 6. 共通モジュール modWinAPI
- 7. 仮想キーコードとは
- 8. Excelでキー入力を監視する
- 9. Ctrl + Shift + F12のような組み合わせを検出する
- 10. ExcelだけならApplication.OnKeyも検討する
- 11. Accessでキー状態を監視する
- 12. DoEventsは何をしているのか
- 13. Sleepを入れる理由
- 14. 性能を確認する
- 15. 機能検証
- 16. 実行手順
- 17. ロールバック方法
- 18. セキュリティとプライバシー
- 19. 権限・デスクトップ境界と戻り値0について
- 20. GetKeyStateとの違い
- 21. このサンプルから分かるWindowsの仕組み
- 22. 落とし穴と注意点
- 23. どんな用途ならGetAsyncKeyStateが向いているか
- 24. 関連する基礎記事
- 25. 公式情報・一次情報
- 26. この次に試してみたいこと
- 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フォームなら KeyDown や KeyPress、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_SHIFT、VK_CONTROL、VK_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で実際に試します。
例として、
AZCtrlEnter
を監視し、
- キー
- 仮想キーコード
- 時刻
を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の場合
- 対象Excelファイルを開く
Alt + F11でVBEを開く- 「挿入」→「標準モジュール」
modWinAPIを作成- 共通APIコードを貼り付ける
modExcelKeyMonitorを作成- Excel用コードを貼り付ける
Sheet1が存在することを確認StartKeyMonitor_Excelを実行- A、Z、Ctrl、Enterなどを押す
Escで終了- Sheet1のログを確認する
Accessの場合
- Accessデータベースを開く
Alt + F11でVBEを開くmodWinAPIを作成modAccessKeyMonitorを作成Form1を作成txtLogテキストボックスを作成cmdStartMonitorボタンを作成- ボタンのクリックイベントへ開始コードを記述
- フォームを開く
- ボタンを押して監視開始
- キー入力を確認
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の完全ガイド
PtrSafe と LongPtr の役割、32bit/64bit OfficeでAPI宣言をどう考えるかを整理しています。
今回の GetAsyncKeyState では、引数が int、戻り値が SHORT なので LongPtr へ置き換える場所はありませんが、FindWindow や GetForegroundWindow など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_ESCAPEVK_CONTROLVK_SHIFTVK_F1~VK_F12- 数字・英字
- マウスボタン
- IME関連キー
- OEMキー
など、Windowsが使用する仮想キーコードの正式な一覧です。
64bit VBA
Microsoft Learn — 64-bit Visual Basic for Applications overview
確認できる内容:
PtrSafeLongPtrLongLong- VBA7
- 32bit/64bit OfficeのDeclare文
- ポインターやハンドルを扱う場合の型
DoEvents
確認できる内容:
- OSへ制御を渡す仕組み
- イベントキューの処理
- ユーザーによる中断を受け付ける用途
- 再入に関する注意点
公式仕様は更新される場合があります。
実際に業務ファイルへ組み込む場合は、Microsoft Learnの最新情報もあわせて確認してください。
26. この次に試してみたいこと
GetAsyncKeyState を理解したら、次は次のようなWin32 APIへ進むと、Windowsの仕組みがさらに見えやすくなります。
GetForegroundWindow- 現在前面にあるウィンドウのハンドルを取得する
GetWindowText- ウィンドウタイトルを取得する
GetCursorPos- 現在のマウスカーソル座標を取得する
FindWindow- ウィンドウを検索してハンドルを取得する
SendMessage/PostMessage- Windowsメッセージを送る仕組みを体験する
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を学び始める題材としても使いやすい例です。

