VBAでWin32 API GetAsyncKeyStateによるキー入力監視

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

VBAでWin32 API GetAsyncKeyStateによるキー入力監視

1. 背景と要件

1.1. キー入力監視の必要性

Officeアプリケーション(ExcelやAccessなど)を用いた業務自動化において、ユーザーのキーボード入力をリアルタイムで検出する必要が生じることがあります。例えば、特定のキーの組み合わせが押されたときにマクロを起動する、バックグラウンドでユーザー操作をログに記録する、あるいはアプリケーションの特定の機能をキーボードショートカットで制御するといったシナリオです。VBA(Visual Basic for Applications)の標準機能では、フォームやコントロールにフォーカスがある状態でのキーイベントは捕捉できますが、グローバルなキー入力を監視することは困難です。

1.2. VBAとWin32 APIの利用

この課題を解決するために、VBAからWindowsの低レベルな機能にアクセスできるWin32 API(Application Programming Interface)を利用します。特に、GetAsyncKeyState関数は、特定の仮想キーが押されているかどうか、または前回呼び出し以降に押されたかどうかを非同期に検出する機能を提供します。これにより、OfficeアプリケーションのUIとは独立して、システム全体のキー入力を監視することが可能になります。 、VBAでGetAsyncKeyStateDeclare PtrSafeで安全に宣言し、ExcelおよびAccessを対象とした具体的なキー入力監視の実装方法、性能チューニング、そして運用上の注意点について詳述します。

2. 設計

2.1. GetAsyncKeyStateの利用方針

GetAsyncKeyState関数は、指定された仮想キーの現在の状態を取得します。戻り値はSHORT型で、以下のビットフラグによって状態が示されます [1]:

  • 最上位ビット (Bit 15): キーが押されている場合 (< 0)。

  • 最下位ビット (Bit 0): 前回GetAsyncKeyStateが呼び出されてからキーが押された場合 (<> 0かつ> 0)。

この特性を利用し、監視ループ内で定期的にGetAsyncKeyStateを呼び出すことで、特定のキーが押された瞬間や、押し続けられている状態を検出します。

2.2. Declare PtrSafeによる宣言

VBAからWin32 APIを呼び出すには、Declareステートメントを使用します。特に、64ビット版のOffice環境でVBAを使用する場合、ポインタの安全性を確保するためにPtrSafeキーワードが必須となります [2][3]。これにより、32ビット/64ビット両方の環境でVBAプロジェクトが動作するようにします。

' Win32 API GetAsyncKeyState関数の宣言
Private Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer

2.3. 監視ループとパフォーマンス考慮

キー入力監視は、通常、無限ループ内でGetAsyncKeyStateを繰り返し呼び出すことで実現します。このループがUIをフリーズさせないよう、以下の点に配慮します。

  • DoEvents関数の利用: ループ内で定期的にDoEvents関数を呼び出すことで、VBAがオペレーティングシステムに制御を渡し、他のイベント(UI更新、ユーザー入力など)が処理される機会を提供します。これにより、UIのフリーズを防ぎ、アプリケーションの応答性を維持できます [5]。

  • 性能最適化: 特にExcelでは、セルの書き込みなどの処理が伴う場合、Application.ScreenUpdating = FalseApplication.Calculation = xlCalculationManualを設定してパフォーマンスを向上させます [6]。これらの設定は、ループ開始時に行い、終了時に元に戻す必要があります。

2.4. キーコードと仮想キーコード

GetAsyncKeyStateは、仮想キーコード(Virtual Key Code: VK_定数)を引数として取ります。例えば、EscキーはVK_ESCAPE(数値は&H1B)、AキーはVK_A(数値は&H41)などです。これらの定数をVBAモジュール内で定義することで、コードの可読性を高めます。

2.5. 処理フロー(Mermaid図)

キー入力監視の一般的な処理フローを以下に示します。

graph TD
    A["開始"] --> B{"監視開始のトリガー"};
    B -- イベント発生例: マクロ実行 --> C["監視ループ開始"];
    C --> D{"GetAsyncKeyStateを呼び出し"};
    D -- キー状態取得 --> E{"仮想キーコードの判定"};
    E -- 特定のキーが押された? --> F{"アクション実行"};
    F --> G["ログ記録/関連処理"];
    G --> H{"停止条件満たした?"};
    H -- No --> I["DoEvents呼び出し"];
    I --> C;
    H -- Yes --> J["監視ループ終了"];
    J --> K["リソース解放/設定復元"];
    K --> L["終了"];

3. 実装

以下に、ExcelおよびAccessでの具体的な実装例を示します。共通のAPI宣言と仮想キーコードは標準モジュールに記述し、アプリケーション固有のロジックは別のモジュールまたはフォームのイベントプロシージャに記述します。

3.1. 共通モジュール (modWinAPI)

' 標準モジュール (例: modWinAPI)

#If VBA7 Then

    ' 64bit/32bit対応 (Office 2010以降)
    Private Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#Else

    ' 32bit専用 (Office 2007以前)
    Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#End If

' 仮想キーコードの定数定義
' 主要なキーのみを抜粋。必要に応じて追加してください。
Public Const VK_LBUTTON As Long = &H1 ' 左マウスボタン
Public Const VK_RBUTTON As Long = &H2 ' 右マウスボタン
Public Const VK_CANCEL As Long = &H3 ' Cancelキー (Ctrl+Break)
Public Const VK_MBUTTON As Long = &H4 ' マウス中央ボタン

Public Const VK_BACK As Long = &H8    ' Backspaceキー
Public Const VK_TAB As Long = &H9     ' Tabキー

Public Const VK_CLEAR As Long = &HC   ' Clearキー
Public Const VK_RETURN As Long = &HD  ' Enterキー

Public Const VK_SHIFT As Long = &H10  ' Shiftキー
Public Const VK_CONTROL As Long = &H11 ' Ctrlキー
Public Const VK_MENU As Long = &H12   ' Altキー
Public Const VK_PAUSE As Long = &H13  ' Pauseキー
Public Const VK_CAPITAL As Long = &H14 ' Caps Lockキー
Public Const VK_ESCAPE As Long = &H1B ' Escキー
Public Const VK_SPACE As Long = &H20  ' Spacebarキー

Public Const VK_PRIOR As Long = &H21  ' Page Upキー
Public Const VK_NEXT As Long = &H22   ' Page Downキー
Public Const VK_END As Long = &H23    ' Endキー
Public Const VK_HOME As Long = &H24   ' Homeキー
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 ' Selectキー
Public Const VK_PRINT As Long = &H2A  ' Printキー
Public Const VK_EXECUTE As Long = &H2B ' Executeキー
Public Const VK_SNAPSHOT As Long = &H2C ' Print Screenキー
Public Const VK_INSERT As Long = &H2D ' Insertキー
Public Const VK_DELETE As Long = &H2E ' Deleteキー
Public Const VK_HELP As Long = &H2F   ' Helpキー

' 0~9のキー ('0' through '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のキー ('A' through '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 ConstVK_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   ' 左Windowsキー
Public Const VK_RWIN As Long = &H5C   ' 右Windowsキー
Public Const VK_APPS As Long = &H5D   ' Applicationsキー

' テンキーのキー (Numpad keys)
Public Const VK_NUMPAD0 As Long = &H60 ' テンキー0
Public Const VK_NUMPAD1 As Long = &H61 ' テンキー1
Public Const VK_NUMPAD2 As Long = &H62 ' テンキー2
Public Const VK_NUMPAD3 As Long = &H63 ' テンキー3
Public Const VK_NUMPAD4 As Long = &H64 ' テンキー4
Public Const VK_NUMPAD5 As Long = &H65 ' テンキー5
Public Const VK_NUMPAD6 As Long = &H66 ' テンキー6
Public Const VK_NUMPAD7 As Long = &H67 ' テンキー7
Public Const VK_NUMPAD8 As Long = &H68 ' テンキー8
Public Const VK_NUMPAD9 As Long = &H69 ' テンキー9
Public Const VK_MULTIPLY As Long = &H6A ' 乗算キー (*)
Public Const VK_ADD As Long = &H6B    ' 加算キー (+)
Public Const VK_SEPARATOR As Long = &H6C ' Separatorキー
Public Const VK_SUBTRACT As Long = &H6D ' 減算キー (-)
Public Const VK_DECIMAL As Long = &H6E ' Decimalキー (.)
Public Const VK_DIVIDE As Long = &H6F  ' 除算キー (/)

' ファンクションキー (F1-F24)
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 ' Num Lockキー
Public Const VK_SCROLL As Long = &H91  ' Scroll Lockキー

' 他のキー
Public Const VK_OEM_1 As Long = &HBA ' For the ';:' key
Public Const VK_OEM_PLUS As Long = &HBB ' For the '+' key
Public Const VK_OEM_COMMA As Long = &HBC ' For the ',' key
Public Const VK_OEM_MINUS As Long = &HBD ' For the '-' key
Public Const VK_OEM_PERIOD As Long = &HBE ' For the '.' key
Public Const VK_OEM_2 As Long = &HBF ' For the '/?' key
Public Const VK_OEM_3 As Long = &HC0 ' For the '`~' key
Public Const VK_OEM_4 As Long = &HDB ' For the '[{' key
Public Const VK_OEM_5 As Long = &HDC ' For the '\|' key
Public Const VK_OEM_6 As Long = &HDD ' For the ']}' key
Public Const VK_OEM_7 As Long = &HDE ' For the ''"' key
' など

3.2. Excelでの実装例

Excelでは、特定のキー入力(例: Ctrl + Cの代わりにCtrl + Insを検出するなど)を監視し、その情報をシートにログとして記録するシナリオを想定します。

' 標準モジュール (例: modExcelKeyMonitor)

Option Explicit

' 監視状態を制御するためのグローバル変数
Public volatile_IsMonitoring As Boolean

'----------------------------------------------------------------------------------------------------------------------
' プロシージャ名: StartKeyMonitor_Excel
' 概要: Excel環境でキー入力監視を開始します。
'       Ctrl+Sが押されるまでキー入力をログに記録し、Escキーで監視を停止します。
' 前提:
'   - modWinAPIモジュールにGetAsyncKeyStateの宣言と仮想キーコード定数が定義されていること。
'   - Sheet1に監視結果をログ記録する準備がされていること。
' 入力: なし
' 出力: なし(Sheet1にログを記録)
' 処理:
'   1. パフォーマンス最適化のため、画面更新と自動計算を一時停止します。
'   2. 監視状態フラグを設定し、無限ループを開始します。
'   3. ループ内でGetAsyncKeyStateを呼び出し、特定のキーの状態をチェックします。
'   4. 検出されたキー入力とタイムスタンプを配列に一時的に格納します。
'   5. DoEventsを呼び出し、他のイベント処理を許可します。
'   6. Escキーが押された場合、監視を停止します。
'   7. 監視終了後、バッファされたログを一括でシートに書き込み、元の設定を復元します。
' 計算量: O(N) where N is the number of detected key events (due to array processing).
'         Each GetAsyncKeyState call is O(1).
' メモリ: O(N * (log_entry_size)) for the log array.
'----------------------------------------------------------------------------------------------------------------------
Sub StartKeyMonitor_Excel()
    Dim keyState As Integer
    Dim lastRow As Long
    Dim logCount As Long
    Dim logBuffer() As Variant ' ログを一時的に保持する配列
    Const BUFFER_SIZE As Long = 100 ' バッファサイズ。適宜調整
    Dim i As Long
    Dim startTime As Double
    Dim endTime As Double

    ' パフォーマンス最適化
    With Application
        .ScreenUpdating = False ' 画面更新を停止 [6]
        .Calculation = xlCalculationManual ' 計算を手動モードに [6]
        .EnableEvents = False ' イベント処理を一時的に無効化
    End With

    ' ログヘッダー
    With ThisWorkbook.Sheets("Sheet1")
        .Cells(1, 1).Value = "日時"
        .Cells(1, 2).Value = "仮想キーコード"
        .Cells(1, 3).Value = "キー名"
        .Cells(1, 4).Value = "状態"
    End With
    lastRow = ThisWorkbook.Sheets("Sheet1").Cells(Rows.Count, 1).End(xlUp).Row + 1
    logCount = 0
    ReDim logBuffer(1 To BUFFER_SIZE, 1 To 4)

    Debug.Print "キー入力監視を開始しました (Escキーで停止)"
    volatile_IsMonitoring = True
    startTime = Timer ' 監視開始時刻

    Do While volatile_IsMonitoring
        ' Escキーが押されたら終了 (GetAsyncKeyStateの最上位ビットが立っているかチェック)
        keyState = GetAsyncKeyState(VK_ESCAPE)
        If (keyState And &H8000) <> 0 Then ' キーが押されている状態
            volatile_IsMonitoring = False
            Exit Do
        End If

        ' その他のキー入力チェック (例: Aキー、Zキー、Ctrlキー)
        ' 監視したいキーをここに追加
        If (GetAsyncKeyState(VK_A) And &H8000) <> 0 Then Call LogKeyEvent_Buffer(VK_A, "A", logBuffer, logCount, lastRow)
        If (GetAsyncKeyState(VK_Z) And &H8000) <> 0 Then Call LogKeyEvent_Buffer(VK_Z, "Z", logBuffer, logCount, lastRow)
        If (GetAsyncKeyState(VK_CONTROL) And &H8000) <> 0 Then Call LogKeyEvent_Buffer(VK_CONTROL, "Ctrl", logBuffer, logCount, lastRow)
        If (GetAsyncKeyState(VK_RETURN) And &H8000) <> 0 Then Call LogKeyEvent_Buffer(VK_RETURN, "Enter", logBuffer, logCount, lastRow)
        ' (注意: このシンプルな実装では、複数のキーが同時に押された場合、すべてのキーがログされる可能性があります)

        ' DoEventsでOSに制御を戻す (UIフリーズ防止) [5]
        DoEvents
        ' 短い時間待機してCPU負荷を軽減 (任意、数ミリ秒程度)
        Application.Wait Now + TimeValue("00:00:00.01") ' 10ミリ秒待機

    Loop

    ' バッファに残っているログを書き出す
    If logCount > 0 Then
        ThisWorkbook.Sheets("Sheet1").Cells(lastRow, 1).Resize(logCount, 4).Value = logBuffer
        lastRow = lastRow + logCount
    End If

    endTime = Timer ' 監視終了時刻
    Debug.Print "キー入力監視を停止しました。"
    Debug.Print "監視時間: " & Format(endTime - startTime, "0.00") & "秒"

    ' 設定を元に戻す
    With Application
        .ScreenUpdating = True
        .Calculation = xlCalculationAutomatic
        .EnableEvents = True
    End With
End Sub

'----------------------------------------------------------------------------------------------------------------------
' プロシージャ名: LogKeyEvent_Buffer
' 概要: キーイベントをログバッファに一時的に格納し、バッファが満杯になったらシートに書き出します。
' 前提:
'   - グローバル変数 volatile_IsMonitoring が宣言されていること。
' 入力:
'   - vKey: 仮想キーコード (Long)
'   - keyName: キーの名称 (String)
'   - ByRef logBuffer: ログを保持する配列 (Variant)
'   - ByRef logCount: 現在のバッファ内のログ数 (Long)
'   - ByRef lastRow: シートに書き込む際の開始行 (Long)
' 出力: なし(logBufferとlogCount、lastRowを更新し、必要に応じてシートに書き込み)
' 計算量: O(1) for adding to buffer, O(BUFFER_SIZE) for writing to sheet.
' メモリ: O(BUFFER_SIZE * log_entry_size)
'----------------------------------------------------------------------------------------------------------------------
Private Sub LogKeyEvent_Buffer(ByVal vKey As Long, ByVal keyName As String, _
                               ByRef logBuffer() As Variant, ByRef logCount As Long, ByRef lastRow As Long)
    ' 最上位ビットが立っている場合のみログ
    If (GetAsyncKeyState(vKey) And &H8000) <> 0 Then
        logCount = logCount + 1
        logBuffer(logCount, 1) = Now
        logBuffer(logCount, 2) = vKey
        logBuffer(logCount, 3) = keyName
        logBuffer(logCount, 4) = "押下"

        ' バッファが満杯になったらシートに書き込む
        If logCount >= UBound(logBuffer, 1) Then
            ThisWorkbook.Sheets("Sheet1").Cells(lastRow, 1).Resize(logCount, 4).Value = logBuffer
            lastRow = lastRow + logCount
            logCount = 0
            ReDim logBuffer(1 To UBound(logBuffer, 1), 1 To 4) ' バッファをリセット
        End If
    End If
End Sub

3.3. Accessでの実装例

Accessでは、フォーム上にキー入力をリアルタイムで表示し、特定のキー(例: Enter)が押されたときにアクションを起こすシナリオを想定します。

' 標準モジュール (例: modAccessKeyMonitor)

Option Explicit

' 監視状態を制御するためのグローバル変数
Public volatile_IsMonitoring As Boolean
Public volatile_AccessFormName As String ' ログ表示対象フォーム名
Public volatile_AccessControlName As String ' ログ表示対象コントロール名

'----------------------------------------------------------------------------------------------------------------------
' プロシージャ名: StartKeyMonitor_Access
' 概要: Access環境でキー入力監視を開始します。
'       指定されたフォームのテキストボックスにキー入力をログに記録し、Escキーで監視を停止します。
' 前提:
'   - modWinAPIモジュールにGetAsyncKeyStateの宣言と仮想キーコード定数が定義されていること。
'   - ログ表示用のフォームとテキストボックスが存在すること。
' 入力:
'   - FormName: ログを表示するフォームの名前 (String)
'   - ControlName: ログを表示するテキストボックスコントロールの名前 (String)
' 出力: なし(指定フォームのコントロールにログを記録)
' 処理:
'   1. 監視状態フラグを設定し、無限ループを開始します。
'   2. ループ内でGetAsyncKeyStateを呼び出し、特定のキーの状態をチェックします。
'   3. 検出されたキー入力とタイムスタンプを指定されたフォームのコントロールに追記します。
'   4. DoEventsを呼び出し、他のイベント処理を許可します。
'   5. Escキーが押された場合、監視を停止します。
'   6. 監視終了後、フォームへのログ表示を停止します。
' 計算量: O(N) where N is the number of detected key events.
'         Each GetAsyncKeyState call is O(1).
' メモリ: O(1) for form control updates (no explicit buffer in this example).
'----------------------------------------------------------------------------------------------------------------------
Sub StartKeyMonitor_Access(ByVal FormName As String, ByVal ControlName As String)
    Dim keyState As Integer
    Dim objForm As Access.Form
    Dim objControl As Access.Control
    Dim logText As String
    Dim startTime As Double
    Dim endTime As Double

    On Error GoTo ErrorHandler

    Set objForm = Forms(FormName)
    Set objControl = objForm.Controls(ControlName)

    ' フォームとコントロールが適切に設定されているか確認
    If objForm Is Nothing Or objControl Is Nothing Then
        MsgBox "指定されたフォームまたはコントロールが見つかりません。", vbCritical
        Exit Sub
    End If

    ' ログ表示コントロールをクリア
    objControl.Value = ""
    logText = "キー入力監視を開始しました (Escキーで停止)" & vbCrLf
    objControl.Value = logText
    objControl.SelStart = Len(objControl.Value) ' カーソルを末尾へ

    Debug.Print "キー入力監視を開始しました (Escキーで停止)"
    volatile_IsMonitoring = True
    volatile_AccessFormName = FormName
    volatile_AccessControlName = ControlName
    startTime = Timer

    Do While volatile_IsMonitoring
        ' Escキーが押されたら終了
        keyState = GetAsyncKeyState(VK_ESCAPE)
        If (keyState And &H8000) <> 0 Then
            volatile_IsMonitoring = False
            Exit Do
        End If

        ' Aキー
        If (GetAsyncKeyState(VK_A) And &H8000) <> 0 Then Call LogKeyEvent_Access(VK_A, "A")
        ' Enterキー
        If (GetAsyncKeyState(VK_RETURN) And &H8000) <> 0 Then Call LogKeyEvent_Access(VK_RETURN, "Enter")
        ' Shiftキー (長押し検出)
        If (GetAsyncKeyState(VK_SHIFT) And &H8000) <> 0 Then Call LogKeyEvent_Access(VK_SHIFT, "Shift (押下中)")
        ' Ctrlキー
        If (GetAsyncKeyState(VK_CONTROL) And &H8000) <> 0 Then Call LogKeyEvent_Access(VK_CONTROL, "Ctrl (押下中)")

        ' DoEventsでOSに制御を戻す (UIフリーズ防止) [5]
        DoEvents
        ' 短い時間待機してCPU負荷を軽減
        Sleep 10 ' 10ミリ秒待機 (Sleep関数は別途宣言が必要)

    Loop

    endTime = Timer
    Debug.Print "キー入力監視を停止しました。"
    Debug.Print "監視時間: " & Format(endTime - startTime, "0.00") & "秒"

    logText = objControl.Value & vbCrLf & "キー入力監視を停止しました。" & vbCrLf
    logText = logText & "監視時間: " & Format(endTime - startTime, "0.00") & "秒"
    objControl.Value = logText
    objControl.SelStart = Len(objControl.Value)

Exit_Sub:
    Set objControl = Nothing
    Set objForm = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラー発生: " & Err.Description, vbCritical
    volatile_IsMonitoring = False
    Resume Exit_Sub
End Sub

' Sleep関数宣言 (VBA7以降対応)
#If VBA7 Then

    Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#Else

    Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If

'----------------------------------------------------------------------------------------------------------------------
' プロシージャ名: LogKeyEvent_Access
' 概要: キーイベントを指定されたAccessフォームのコントロールに追記します。
' 前提:
'   - グローバル変数 volatile_AccessFormName, volatile_AccessControlName が設定されていること。
' 入力:
'   - vKey: 仮想キーコード (Long)
'   - keyName: キーの名称 (String)
' 出力: なし(フォームのコントロールを更新)
' 計算量: O(L) where L is the current length of the text in the control.
' メモリ: O(1)
'----------------------------------------------------------------------------------------------------------------------
Private Sub LogKeyEvent_Access(ByVal vKey As Long, ByVal keyName As String)
    Static lastLoggedKey As Long ' 最後にログに記録したキーを保持
    Static lastLoggedTime As Double ' 最後にログに記録した時刻を保持

    ' 同じキーが連続して高速で押され続けた場合、ログの重複を防ぐための簡易的な処理
    ' この実装ではキーが押されっぱなしでもログは1回のみだが、LogKeyEvent_Accessは
    ' ループごとに呼ばれるため、これを調整する必要がある。
    ' GetAsyncKeyStateの最下位ビット (0x0001) を使って、前回呼び出し以降に押されたことを検出する。
    Dim keyState As Integer
    keyState = GetAsyncKeyState(vKey)

    ' 最下位ビットが設定されている(前回呼び出し以降に押された)場合のみログ
    If (keyState And &H0001) <> 0 Then
        Dim objForm As Access.Form
        Dim objControl As Access.Control
        Set objForm = Forms(volatile_AccessFormName)
        Set objControl = objForm.Controls(volatile_AccessControlName)

        Dim logEntry As String
        logEntry = Format(Now, "yyyy/mm/dd hh:mm:ss") & " - VK: " & vKey & " (" & keyName & ")"
        objControl.Value = objControl.Value & logEntry & vbCrLf
        objControl.SelStart = Len(objControl.Value) ' カーソルを末尾へ

        Set objControl = Nothing
        Set objForm = Nothing
    End If
End Sub

Accessでの実行準備:

  1. VBAエディタ(Alt + F11)を開き、modWinAPIモジュールとmodAccessKeyMonitorモジュールをインポートまたは新規作成して上記コードを貼り付けます。

  2. 新しいフォームを作成し、そのフォームにテキストボックスコントロール(例: txtLog)を配置します。

  3. フォームを「Form1」などの名前で保存し、デザインビューで開いてテキストボックスの名前プロパティをtxtLogに設定します。

  4. フォームにコマンドボタン(例: cmdStartMonitor)を配置し、クリックイベントプロシージャに以下のコードを記述します。

' フォームモジュール (例: Form_Form1)

Private Sub cmdStartMonitor_Click()
    Call StartKeyMonitor_Access("Form1", "txtLog")
End Sub

4. 検証

4.1. 機能検証

  • キー入力の検出: StartKeyMonitor_ExcelまたはStartKeyMonitor_Accessを実行後、監視対象のキー(A, Z, Ctrl, Enterなど)を押してみてください。ExcelのSheet1やAccessのフォーム上のテキストボックスに、押されたキーと日時が正確にログ記録されることを確認します。

  • 停止条件の確認: Escキーを押すことで、監視ループが停止し、ログ記録が終了することを確認します。

  • 複数キーの同時押下: Ctrlキーを押しながらAキーを押すなど、複数のキーを同時に押した場合の動作を確認します。GetAsyncKeyStateの特性上、監視対象のキーが同時に押されていれば、それぞれが検出されます。

4.2. 性能検証

  • UI応答性: 監視中にExcelのシートをスクロールしたり、Accessのフォーム上の他のコントロールを操作したりして、アプリケーションのUIがフリーズせずに応答性を保っていることを確認します。

    • Application.ScreenUpdating = FalseDoEventsがない場合、キー入力監視ループがUIスレッドを占有し、アプリケーションが数秒から数十秒フリーズする可能性があります。これらの最適化を適用することで、体感的なUIフリーズがほぼゼロになり、滑らかな操作感が維持されます。
  • CPU使用率: タスクマネージャーを開き、ExcelまたはAccessプロセスのCPU使用率を監視します。監視ループが常にCPUを100%消費するような状況ではなく、DoEventsApplication.Wait(Excel)またはSleep(Access)によって適度にCPUリソースが解放されていることを確認します。一般的な環境で、監視中のCPU使用率は数%から20%程度に収まることが期待されます。過度なCPU消費は、DoEventsや待機時間の調整が必要です。

  • ログ記録速度: Excelの場合、BUFFER_SIZEを大きくすることで、シートへの書き込み回数が減り、パフォーマンスが向上します。例えば、BUFFER_SIZEが1の場合と比較して100に設定した場合、シートへの書き込みに起因するオーバーヘッドが約1/100に削減され、特に大量のキー入力があった際の書き込み処理の効率が大幅に向上します。

5. 運用

5.1. 実行手順

  1. VBAエディタを開く: 対象のExcelブックまたはAccessデータベースを開き、Alt + F11キーを押してVBAエディタを起動します。

  2. モジュールのインポート/作成:

    • プロジェクトエクスプローラーで対象のプロジェクトを選択し、挿入 > 標準モジュールを選択します。

    • 作成されたモジュールにmodWinAPIのコードを貼り付け、モジュール名をmodWinAPIに変更します。

    • 同様に、Excelの場合はmodExcelKeyMonitor、Accessの場合はmodAccessKeyMonitorを作成してコードを貼り付けます。

  3. Excelの場合:

    • Sheet1に監視結果がログされるため、必要であればシート名を変更し、コード内のシート名も修正します。

    • StartKeyMonitor_Excelプロシージャを実行するには、VBAエディタでプロシージャ内にカーソルを置き、F5キーを押すか、Excelの開発タブ > マクロから選択して実行します。

  4. Accessの場合:

    • ログ表示用のフォーム(例: Form1)とテキストボックス(例: txtLog)が事前に作成されていることを確認します。

    • フォームのボタンのクリックイベントからStartKeyMonitor_Access "Form1", "txtLog"を呼び出すように設定します。

    • フォームを開き、ボタンをクリックして監視を開始します。

5.2. ロールバック方法

キー入力監視機能が不要になった場合や、問題が発生した場合は、以下の手順でロールバックできます。

  1. 監視の停止: Escキーを押すか、アプリケーションを閉じることで、実行中の監視マクロを停止します。無限ループに入ってしまった場合は、Ctrl + Breakキーで実行を中断できます。

  2. VBAモジュールの削除: VBAエディタでmodWinAPImodExcelKeyMonitormodAccessKeyMonitorなどのモジュールを右クリックし、削除を選択します。プロンプトが表示されたら「エクスポートしない」を選択します。

  3. Accessフォームの削除: Accessの場合、作成したログ表示用フォームとボタンを削除します。

  4. 設定の復元: ExcelでApplication.ScreenUpdatingApplication.Calculationが手動モードのままになっている場合は、VBAエディタのイミディエイトウィンドウでApplication.ScreenUpdating = TrueApplication.Calculation = xlCalculationAutomaticを実行して元に戻します。

5.3. セキュリティに関する考慮事項

Win32 APIの利用は強力な機能を提供しますが、誤った使用はシステムに不安定性をもたらす可能性があります。

  • 信頼できるソースのみからコードを使用する: 外部から入手したAPI利用コードは、内容を十分に理解してから導入してください。

  • デジタル署名: マクロを含むExcelブックやAccessデータベースは、信頼できる発行元からのデジタル署名を付与することで、ユーザーに安心して利用してもらうことができます。

  • 権限: GetAsyncKeyStateはユーザーレベルのAPIであるため、特別な管理者権限は通常不要ですが、システム全体を監視するような複雑なシナリオでは権限の問題が発生する可能性があります。

6. 落とし穴と注意点

6.1. DoEventsの過剰な利用

DoEventsはUIの応答性を保つために重要ですが、頻繁に呼び出しすぎるとCPU負荷が高まる可能性があります。ループの実行間隔を調整し(例: Application.WaitSleep関数で短い待機時間を設ける)、適切なバランスを見つけることが重要です。待機時間が短すぎるとCPUを浪費し、長すぎるとキー入力検出の遅延につながります。

6.2. パフォーマンスボトルネック

キー入力監視ループ内で、時間がかかる処理(例: 大量のセルへの書き込み、データベースへの頻繁な書き込み)を行うと、パフォーマンスが著しく低下し、UIがフリーズする原因となります。ログ記録などは配列バッファを活用してまとめて書き込むなど、処理の効率化を図るべきです。

6.3. 仮想キーコードの誤認識

キーボードレイアウトや言語設定によっては、特定の仮想キーコードが意図した物理キーと一致しない場合があります。主要なキーは共通ですが、地域固有のキーや特殊なキーについては事前にテストが必要です。また、GetAsyncKeyStateは物理的なキーの状態を報告するため、Modifierキー(Shift, Ctrl, Alt)が押されているかどうかは、個別にチェックする必要があります。

6.4. 管理者権限の問題

基本的にGetAsyncKeyStateはユーザーセッション内のキーイベントを監視するため、Officeアプリケーションが起動しているプロセスと同じ権限で動作します。しかし、UAC(ユーザーアカウント制御)の昇格されたプロセス(管理者として実行されているアプリケーション)からのキー入力は、標準権限のOfficeアプリケーションでは捕捉できない場合があります。

7. まとめ

本記事では、VBAでWin32 APIのGetAsyncKeyState関数を利用し、キー入力を監視する手法について解説しました。Declare PtrSafeによる安全なAPI宣言から、ExcelおよびAccessでの具体的な実装例、パフォーマンス最適化、そして運用上の注意点までを網羅しています。

GetAsyncKeyStateはOfficeアプリケーションの標準機能では実現が難しいグローバルなキー入力監視を可能にし、業務自動化の可能性を広げます。DoEventsApplication.ScreenUpdating = Falseなどの適切なパフォーマンスチューニングを施すことで、UIの応答性を維持しながら効率的なキー入力監視システムを構築できます。実装においては、仮想キーコードの正確な理解と、無限ループとならないための停止条件の実装が特に重要です。これらの知見を活用し、より高度なOffice自動化を実現してください。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

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