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

Tech

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

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

背景/要件

Microsoft Officeアプリケーション(ExcelやAccessなど)において、ユーザーがアクティブなコントロールにフォーカスしていなくても、特定のキー入力(ショートカットキーなど)をバックグラウンドで監視し、それに応じてVBAマクロをトリガーしたい場合があります。例えば、データベースフォームを開いている最中に Ctrl+Shift+A を押したら特定の処理を実行するなどです。

VBAの標準機能では、このようなグローバルなキー入力監視は困難ですが、Windows API(Win32 API)の GetAsyncKeyState 関数を利用することで実現可能です。本記事では、外部ライブラリに依存せず、GetAsyncKeyStateDeclare PtrSafe で宣言し、ExcelおよびAccessを対象に実務レベルで再現可能なキー入力監視の実装方法、性能チューニング、運用上の注意点、そして潜在的な落とし穴を解説します。

設計

キー入力監視の設計は以下の要素で構成されます。

  • API宣言: GetAsyncKeyState 関数をVBAで使用するために Declare PtrSafe ステートメントで宣言します。64ビット版Officeに対応するため PtrSafe が必須です。

  • 仮想キーコード: 監視したいキーに対応する仮想キーコード(例: VK_CONTROLVK_S)を定義します。

  • ポーリング機構: GetAsyncKeyState は特定の瞬間のキー状態を返すため、定期的にこの関数を呼び出す(ポーリングする)必要があります。Excelでは Application.OnTime を、Accessではフォームのタイマーイベントを利用します。

  • 状態管理: キーの「押し下げ」や「解放」を正確に検出するため、前回のポーリング時のキー状態を記録し、現在の状態と比較します。これにより、キーを押し続けた際にイベントが繰り返しトリガーされることを防ぎます。

  • パフォーマンス: ポーリング間隔の調整やUI更新の抑制により、CPU負荷を最小限に抑えます。

キー入力監視の一般的な処理フローは以下のようになります。

flowchart TD
    A["スタート: キー入力監視の開始"] --> B{"監視対象キーリストの初期化"};
    B --> C{"タイマー設定 (例: 50ms)"};
    C --> D("キー監視ループ開始");
    D -- 定期実行 --> E{"GetAsyncKeyState呼び出し"};
    E -- 仮想キーコードを渡す --> F["キー状態の取得"];
    F --> G{"現在のキー状態を判断"};
    G -- キーが押されている? --> H{"前回の状態と比較"};
    H -- 変化あり (押された)? --> I["特定アクションの実行 (例: マクロ呼び出し)"];
    H -- 変化あり (離された)? --> J["状態更新"];
    J --> D;
    G -- キーが押されていない? --> J;
    I --> J;
    K["停止: キー入力監視の終了"] --> L{"タイマー解除"};
    L --> M("監視ループ終了");

実装

コード1: Excelでのキー入力監視

特定のキーの同時押し(例: Ctrl + Alt + S)を監視し、イベントが発生したらメッセージボックスを表示する例です。Application.OnTime を使用して定期的に監視します。

' 標準モジュール (例: Module1) に記述

#If VBA7 And Win64 Then

    ' 64ビット版Office用
    Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#Else

    ' 32ビット版Office用
    Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#End If

' 監視対象の仮想キーコード
' Learn.Microsoft.com: Virtual-Key Codes (更新日: 2023年2月24日, Microsoft) [1]
Private Const VK_CONTROL As Long = &H11   ' Ctrlキー
Private Const VK_MENU As Long = &H12      ' Altキー
Private Const VK_S As Long = &H53         ' Sキー

' 監視状態と前回のキー状態を保持する変数
Private p_IsMonitoring As Boolean
Private p_LastCtrlState As Boolean
Private p_LastAltState As Boolean
Private p_LastSState As Boolean

' タイマー解除用
Private p_NextCheckTime As Date

'------------------------------------------------------------
' 機能: キー入力監視を開始する
'------------------------------------------------------------
Public Sub StartKeyMonitor()
    If p_IsMonitoring Then Exit Sub

    p_IsMonitoring = True
    p_LastCtrlState = False
    p_LastAltState = False
    p_LastSState = False

    ' 監視開始メッセージ
    Application.StatusBar = "キー監視中... (Ctrl+Alt+S でイベント)"

    ' 監視ループを開始
    Call ScheduleKeyCheck
End Sub

'------------------------------------------------------------
' 機能: キー入力監視を停止する
'------------------------------------------------------------
Public Sub StopKeyMonitor()
    If Not p_IsMonitoring Then Exit Sub

    p_IsMonitoring = False
    Application.StatusBar = False ' ステータスバーをクリア

    ' 予約されたタイマーを解除
    On Error Resume Next ' タイマーがない場合のエラーを無視
    Application.OnTime EarliestTime:=p_NextCheckTime, Procedure:="CheckKeyState", Schedule:=False
    On Error GoTo 0

    MsgBox "キー監視を停止しました。", vbInformation
End Sub

'------------------------------------------------------------
' 機能: キーの状態を定期的にチェックするメインルーチン
'       (Application.OnTimeによって呼び出される)
'------------------------------------------------------------
Private Sub CheckKeyState()
    ' 前提: p_IsMonitoring = True
    ' 処理: GetAsyncKeyStateでキー状態を取得し、前回の状態と比較してアクションを実行
    ' 計算量: O(1)
    ' メモリ条件: わずかなプライベート変数
    If Not p_IsMonitoring Then Exit Sub

    Dim currentCtrlState As Boolean
    Dim currentAltState As Boolean
    Dim currentSState As Boolean

    ' GetAsyncKeyState関数でキーの状態を取得
    ' Learn.Microsoft.com: GetAsyncKeyState function (更新日: 2023年2月24日, Microsoft) [2]
    currentCtrlState = (GetAsyncKeyState(VK_CONTROL) And &H8000) <> 0
    currentAltState = (GetAsyncKeyState(VK_MENU) And &H8000) <> 0
    currentSState = (GetAsyncKeyState(VK_S) And &H8000) <> 0

    ' Ctrl + Alt + S の同時押しを検出
    If currentCtrlState And currentAltState And currentSState And _
       (Not p_LastCtrlState Or Not p_LastAltState Or Not p_LastSState) Then
        ' 新たに同時押しが検出された場合のみアクションを実行
        Call PerformAction
        ' イベントがトリガーされたら、その瞬間のキー状態をリセットし、
        ' 同じ押し続けで複数回トリガーされないようにする
        p_LastCtrlState = True
        p_LastAltState = True
        p_LastSState = True
    ElseIf Not (currentCtrlState And currentAltState And currentSState) Then
        ' いずれかのキーが離されたら状態を更新
        p_LastCtrlState = currentCtrlState
        p_LastAltState = currentAltState
        p_LastSState = currentSState
    End If

    ' 次のチェックをスケジュール (例: 50ミリ秒後)
    Call ScheduleKeyCheck
End Sub

'------------------------------------------------------------
' 機能: キー監視ルーチンの次回の実行をスケジュールする
'------------------------------------------------------------
Private Sub ScheduleKeyCheck()
    ' 入力: なし
    ' 出力: なし (Application.OnTimeにより次回実行を予約)
    ' 前提: p_IsMonitoring = True
    ' 計算量: O(1)
    ' メモリ条件: わずかなプライベート変数
    p_NextCheckTime = Now + TimeSerial(0, 0, 0) + TimeSerial(0, 0, 0.05) ' 50ミリ秒後
    Application.OnTime EarliestTime:=p_NextCheckTime, Procedure:="CheckKeyState", Schedule:=True
End Sub

'------------------------------------------------------------
' 機能: 特定のキーイベントが発生した際に実行するアクション
'------------------------------------------------------------
Private Sub PerformAction()
    ' ここに実行したいマクロ処理を記述
    MsgBox "Ctrl + Alt + S が押されました!", vbInformation
    ' 例: 特定のシートを選択したり、計算を実行したりする
    ' Worksheets("データ").Activate
    ' Application.Run "MyCustomMacro"
End Sub

実行手順 (Excel)

  1. Excelを開き、Alt + F11 を押してVBAエディターを開きます。

  2. 左側の「VBAProject」ツリーで「Microsoft Excel Objects」の下にある「ThisWorkbook」をダブルクリックし、以下のコードを貼り付けます。これにより、ブックを開いたときに監視が開始され、閉じたときに停止します。

    ' ThisWorkbookモジュールに記述
    Private Sub Workbook_Open()
        Call Module1.StartKeyMonitor
    End Sub
    
    Private Sub Workbook_BeforeClose(Cancel As Boolean)
        Call Module1.StopKeyMonitor
    End Sub
    
  3. 「挿入」メニューから「標準モジュール」を選択し、新しいモジュール(例: Module1)に上記の StartKeyMonitor から PerformAction までのコードを貼り付けます。

  4. Excelブックを保存し、一旦閉じます。

  5. 再度ブックを開くと、ステータスバーに「キー監視中…」と表示されます。

  6. Ctrl + Alt + S を同時に押すと、メッセージボックスが表示されます。

ロールバック方法 (Excel)

  1. Excelを開き、Alt + F11 でVBAエディターを開きます。

  2. 「ThisWorkbook」モジュールと「Module1」(または作成した標準モジュール)を開き、上記で追加したコードを全て削除します。

  3. ブックを保存して閉じれば、変更は元に戻ります。

コード2: Accessでのキー入力監視

Accessでは、フォームのタイマーイベントを使用して同様のキー監視を実現できます。ここでは、特定のフォームが開いている間だけ F12 キーの押し下げを監視し、メッセージを表示する例を示します。

' 標準モジュール (例: modKeyMonitorAccess) に記述

#If VBA7 And Win64 Then

    Declare PtrSafe Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#Else

    Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
#End If

' 監視対象の仮想キーコード
Private Const VK_F12 As Long = &H7B ' F12キー

' 前回のキー状態を保持する変数
Private p_LastF12State As Boolean

'------------------------------------------------------------
' 機能: フォームのタイマーイベントから呼び出されるキーチェックルーチン
' 入力: frm - 呼び出し元のフォームオブジェクト
' 出力: なし
' 前提: フォームのタイマーイベントで定期的に呼び出される
' 計算量: O(1)
' メモリ条件: わずかなプライベート変数
'------------------------------------------------------------
Public Sub CheckAccessKeyState(frm As Form)
    Dim currentF12State As Boolean

    currentF12State = (GetAsyncKeyState(VK_F12) And &H8000) <> 0

    If currentF12State And Not p_LastF12State Then
        ' F12キーが新たに押された
        Call PerformAccessAction(frm)
    End If

    ' キー状態を更新
    p_LastF12State = currentF12State
End Sub

'------------------------------------------------------------
' 機能: 特定のキーイベントが発生した際に実行するアクション (Access版)
' 入力: frm - 呼び出し元のフォームオブジェクト
' 出力: なし
' 前提: CheckAccessKeyStateから呼び出される
' 計算量: O(1)
' メモリ条件: わずかなプライベート変数
'------------------------------------------------------------
Private Sub PerformAccessAction(frm As Form)
    ' ここに実行したいAccessでの処理を記述
    MsgBox "F12キーが押されました! フォーム名: " & frm.Name, vbInformation, "キーイベント検出"
    ' 例: 特定のレコードに移動したり、レポートを開いたりする
    ' DoCmd.OpenReport "Rpt_顧客一覧", acViewPreview
End Sub

実行手順 (Access)

  1. Accessデータベースを開き、Alt + F11 を押してVBAエディターを開きます。

  2. 「挿入」メニューから「標準モジュール」を選択し、新しいモジュール(例: modKeyMonitorAccess)に上記の Declare から PerformAccessAction までのコードを貼り付けます。

  3. 新しいフォームを作成(例: frmMonitor)。

  4. フォームのデザインビューを開き、フォームのプロパティシートで以下を設定します。

    • タイマー間隔: 50 (50ミリ秒ごとにイベントを発生させる)
  5. フォームの On Timer イベントに以下のイベントプロシージャを記述します。

    ' frmMonitorフォームのモジュールに記述
    Private Sub Form_Timer()
        Call modKeyMonitorAccess.CheckAccessKeyState(Me)
    End Sub
    
    Private Sub Form_Load()
        ' 初期状態をリセット
        modKeyMonitorAccess.p_LastF12State = False
    End Sub
    
  6. フォームを保存し、フォームビューで開きます。

  7. フォームが開いている状態で F12 キーを押すと、メッセージボックスが表示されます。

ロールバック方法 (Access)

  1. Accessデータベースを開き、Alt + F11 でVBAエディターを開きます。

  2. 作成した標準モジュール(例: modKeyMonitorAccess)を開き、コードを全て削除します。

  3. フォーム frmMonitor をデザインビューで開き、プロパティシートの「タイマー間隔」を 0 に戻し、「On Timer」イベントプロシージャを削除します。

  4. フォームを保存し、モジュールを閉じれば、変更は元に戻ります。フォーム自体が不要であれば削除します。

検証

  • キー検出: 指定したキー(Excel例では Ctrl+Alt+S、Access例では F12)を押して、期待通りにメッセージボックスが表示されることを確認します。

  • 誤動作の確認: 関係ないキーを押した際にイベントが発生しないこと、キーを押しっぱなしにした際にイベントが繰り返し発生しないこと(一度の押し下げで一度だけトリガーされること)を確認します。

  • 停止機能: 監視停止ルーチンが正しく動作し、停止後にキー入力が検出されないことを確認します。

運用

  • 監視間隔: Application.OnTime やフォームのタイマー間隔は、パフォーマンスと応答性のバランスを考慮して設定します。短い間隔(例: 10ms)は応答性が向上しますが、CPU負荷が増加します。一般的な用途では 50ms ~ 100ms が妥当です。

  • エラー処理: On Error Resume Next を適切に配置し、予期せぬエラー(例: Application.OnTime の解除失敗)でアプリケーションが停止しないようにします。

  • リソース管理: 監視開始時にリソース(例: Excelのステータスバー)を占有し、終了時に解放するように設計します。

落とし穴

  1. 32ビット/64ビット問題: VBAの Declare ステートメントは、Officeのビット数によって記述が変わります。PtrSafe キーワードを付けた Declare PtrSafe は64ビット版Officeで必須です。上記のコードは #If VBA7 And Win64 Then を用いて両対応しています。この記述がない場合、64ビット環境で実行時にエラーが発生します[3]。

  2. キー状態の解釈: GetAsyncKeyState の戻り値は SHORT 型であり、ビット単位で状態を示します。

    • 高位ビットがセットされている(戻り値が負数): 関数呼び出し以降にキーが押されていた。

    • 最下位ビットがセットされている(戻り値の & 11): キーが現在押されている。 本記事のコードでは高位ビット (&H8000) をチェックして、キーが「現在押されている状態」を検出しています。これは一般的なキー検出には十分ですが、細かなキー状態の履歴を追う場合は、最下位ビットも考慮に入れる必要があります[2]。

  3. パフォーマンスとCPU負荷: GetAsyncKeyState を非常に短い間隔でポーリングすると、CPU使用率が高くなる可能性があります。特に、複雑な処理を PerformAction に記述する場合、システム全体の応答性に影響を与えることがあります。

    • 性能チューニング:

      • ポーリング間隔の調整: 10ms (0.01秒) 未満は避けるべきです。上記例では 50ms (0.05秒) を採用しており、これは一般的なユーザー操作には十分な応答性を持ちながら、CPU負荷を抑えるバランスの良い値です。例えば、20ms 間隔で監視する場合、CPU 使用率はアイドル時と比較して数%程度増加する可能性がありますが、100ms 間隔であればその影響はさらに小さくなります。

      • DoEvents の利用: 長時間かかる処理を PerformAction 内で実行する場合、DoEvents を適宜挟むことで、UIの応答性を維持できますが、ポーリングループ内での乱用は避けるべきです。

      • Application.ScreenUpdating = False (Excel): PerformAction 内でExcelのシート更新を伴う場合、この設定で描画処理を一時停止し、処理速度を向上させます。

      • Application.Calculation = xlCalculationManual (Excel): 大量の計算を伴う場合、手動計算に切り替えることで性能を改善できます。

  4. キー入力の排他制御: GetAsyncKeyState はグローバルなキー状態を取得するため、他のアプリケーションのキー入力も検出します。これは望ましい動作ですが、特定のアプリケーションに限定したい場合は、より高度なキーボードフック(SetWindowsHookEx)を検討する必要があります。ただし、これはVBAで実装するには複雑であり、本記事の要件外です。

  5. モジュール変数のスコープ: p_IsMonitoring のような状態管理変数は、標準モジュールレベルで Private で宣言し、不必要なグローバル名前空間の汚染を避けるべきです。

まとめ

VBAでWin32API GetAsyncKeyState を利用することで、Officeアプリケーションのバックグラウンドで特定のキー入力イベントを柔軟に監視できます。Declare PtrSafe によるAPI宣言、仮想キーコードの利用、そして Application.OnTime やフォームのタイマーイベントによるポーリングが実装の鍵となります。性能チューニングとしてポーリング間隔の最適化やUI更新の抑制を考慮することで、実務レベルで安定して動作するソリューションを構築することが可能です。32ビット/64ビット環境の違いやキー状態の正しい解釈といった「落とし穴」を理解し適切に対応することで、堅牢なシステムを開発できます。


脚注: [1] Microsoft Learn. 「Virtual-Key Codes (winuser.h) – Win32 apps」. 更新日: 2023年2月24日. https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes (参照日: {{jst_today}}). [2] Microsoft Learn. 「GetAsyncKeyState function (winuser.h) – Win32 apps」. 更新日: 2023年2月24日. https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate (参照日: {{jst_today}}). [3] Microsoft Learn. 「Declare Statement – VBA」. 更新日: 2023年12月15日. https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/declare-statement (参照日: {{jst_today}}).

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

コメント

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