見出し画像

【VBA】任意のキーを非アクティブ状態のウィンドウに送信する方法

Excel VBAで、任意のキー(キーストローク)を非アクティブ状態のウィンドウに送信するコードのサンプルです。
「非アクティブ状態のウィンドウ」という所がミソ。
非アクティブのままにしておけば、バックグラウンドで処理継続できます。
またウィンドウハンドルを指定してキーを送り込むので、その他の方法に比べて事故率も低くなります。(これ結構大事)

全体像はこちら。
【概要】
・操作対象のウィンドウハンドルを取得
・MapVirtualKey関数で仮想キーコードから送信メッセージ(LPARAM)を作成
・PostMessage関数で対象ウィンドウへメッセージ送信

コードはこちら。(通常キー送信時)

'ウィンドウ情報取得用API
Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, ByVal lpClassName As String, ByVal IpWindowName As String) As LongPtr
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal IpWindowName As String) As LongPtr
'------------------------
'キーコード送信用API
Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As LongPtr, ByVal wparam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As LongPtr, ByVal wMapType As LongPtr) As LongPtr
'------------------------ 

Sub Sample()
    
    'メモ帳を非アクティブ状態で起動
    Shell "Notepad.exe", vbMinimizedNoFocus
    
    'メモ帳のハンドル取得
    Dim hWnd As LongPtr
    Dim hWnd_Edit As LongPtr
    hWnd = FindWindow("notepad", vbNullString)
    hWnd_Edit = FindWindowEx(hWnd, 0, "Edit", vbNullString)
    
    'メモ帳にRキーを送信する
    Dim lParam As LongPtr
    Const WM_CHAR = &H102
    Const VK_R = &H52
    lParam = 1 + MapVirtualKey(VK_R, 0) * (2 ^ 16)
    PostMessage hWnd_Edit, WM_CHAR, VK_R, lParam

End Sub

非アクティブ状態のメモ帳に「r」を1文字打ち込んでいます。

VK_Rは仮想キーコードの定数で、ここから確認可能。
 仮想キーコード (Winuser.h) - Win32 apps | Microsoft Learn
Rキーは16進数表記で0x52とあり、VBAではプレフィックス「&H」を付けることで16進数表記で整数リテラルを記述できるため&H52。
※&H52は16進数の52、10進数の82。
 Windows標準電卓のHEX(16進数)、DEC(10進数)を確認するのが一番手軽。

lParamパラメーターには、メッセージを生成したキーストロークに関する追加情報が含まれています。メッセージによって値が異なり、上記WM_CHARの場合は&H130001。
WM_CHARメッセージのパラメーターはここで確認可能。結構分りやすい。
 WM_CHAR メッセージ (Winuser.h) - Win32 apps | Microsoft Learn
スキャンコードはここで確認可能。
 キーボード入力の概要 - Win32 apps | Microsoft Learn
またC言語のネット記事が比較的豊富で、魔界の仮面弁士様の解説有。
Re[7]: C#で他アプリにALT+"Fキー"などの押した状態を送信 (wankuma.com)
ただ余り深く考えても面倒なので、ネットのコピペか、上記コードみたくMapVirtuakKey関数で変換しちゃっても良い…でしょう。

最後の行で、PostMessage関数を使って対象ハンドルにメッセージを送信。
※因みにPostMessage関数は非同期処理、SendMessage関数は同期処理。
 同期処理だと対象ハンドルからリターンが返るまで待機継続してしまう
 ため、単に任意のキーを送るだけならPostMessage関数を選択。
第一引数:送信先のハンドル
第二引数:アクション
     通常キーならWM_CHAR(1文字送信)
     特殊キーならWM_SYSCHAR(1文字送信)
第三引数:仮想キーコード
第四引数:送信メッセージ(lParamパラメーター)


基本的な流れは以上です。
続いて、特殊キーを送信するコードはこちら。(特殊キー送信時)

'ウィンドウ情報取得用API
Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hwndParent As LongPtr, ByVal hwndChildAfter As LongPtr, ByVal lpClassName As String, ByVal IpWindowName As String) As LongPtr
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal IpWindowName As String) As LongPtr
'------------------------
'キーコード送信用API
Private Declare PtrSafe Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hWnd As LongPtr, ByVal wMsg As LongPtr, ByVal wparam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As LongPtr, ByVal wMapType As LongPtr) As LongPtr
'------------------------
Sub Sample2()
    
    'メモ帳をアクティブ状態で起動
    Shell "Notepad.exe", vbNormalFocus
    
    'メモ帳のハンドル取得
    Dim hWnd As LongPtr
    Dim hWnd_Edit As LongPtr
    hWnd = FindWindow("notepad", vbNullString)
    hWnd_Edit = FindWindowEx(hWnd, 0, "Edit", vbNullString)
    
    'メモ帳にAlt+Rキーを送信する
    Dim lParam As LongPtr
    Const WM_SYSCHAR = &H106
    Const VK_F = &H46
    lParam = 1 + MapVirtualKey(VK_F, 0) * (2 ^ 16)
    lParam = "&H20" & Hex(lParam)
    PostMessage hWnd_Edit, WM_SYSCHAR, VK_F, lParam

メモ帳に「Alt+r」を1文字送信しています。
ただし、動作確認しやすいようメモ帳はアクティブ状態で起動しました。
メモ帳の「ファイル(F)」メニューが開かれ、Alt+rが送信されたことが確認できるかと思います。
特殊キーとはAlt(メニュー)やF10等のことで、今回は特殊キー(システムキー)としての「R」、要するに「Alt+R」状態のキーが1文字送信されます。

PostMessage関数の第二引数アクションはWM_SYSCHAR。
第四引数の送信メッセージ(lParamパラメーター)は、上記WM_SYSCHARの場合は&H20210001。
上記コードでは、MapVirtualKey関数で変換した値(10進数2162688)+1の和(10進数2162689)を、一旦Hex関数で16進数に変換し、その頭に&H20を付けました。
つまり第二引数WM_SYSCHARで特殊キーを送信するときのlParamの値は&H20●●0001。(●●の部分にはスキャンコード2桁が入る)
WM_SYSCHARメッセージのパラメーターはここで確認可能。
 WM_SYSDEADCHAR メッセージ (Winuser.h) - Win32 apps | Microsoft Learn
スキャンコードはここで確認可能。
 キーボード入力の概要 - Win32 apps | Microsoft Learn


以上、非アクティブ状態のウィンドウに任意のキーを送信する方法でした。「任意のキーを送信する方法」とネットで検索してみると
 ・VBAのSendKeysステートメント
 ・WshShellのSendKeysメソッド
 ・Win APIのkeybd_event関数
辺りをよく見かけますが、どれも対象ウィンドウがアクティブでないと使えません。いちいちキー送信の度にアクティブ化させるのも面倒だし、アクティブ化したタイミングで意図しない介入(人的操作)が発生しないとも限りません。
どうしてもキー送信する処理が必要なら、今回の方法が堅実ですかね。

(追記)
完全に蛇足ですが、PostMessage関数を調べることになったキッカケを残しておきます。
いつも業務で使ってる共有フォルダが常にパンパンで、ファイル移動できなかったときに出るWindowsダイアログ「項目のコピー」「項目の移動」「1件の中断された処理」の再試行ボタンを無理やり押したかったから。この再試行ボタンはAlt+Rで押せる仕様になっていたため、非アクティブ状態のダイアログを無限に押し続けるマクロを作りたかったのがキッカケですかね。
備忘として、このエクスプローラーのファイル移動時のダイアログは
"OperationStatusWindow"クラス名で、更にその子ウィンドウとして"DirectUIHWND"クラス名のウィンドウがあり、ここにAlt+Rをぶち込めば
再試行できたので、何かのために残しておきます。
共有フォルダに関しては、書きたいネタがまだあるので、別の機会に投稿予定。

この記事が気に入ったらサポートをしてみませんか?