他のプログラムにボタン押下のメッセージ(SendMessage)を送る
これもまたよくある例だけど。
やってることはSettingButtonsを実行すると,対象ウィンドウからボタンを見つけてきて,
ボタン名とハンドルを紐付けconfのコレクションに保存し,pushButton(ボタン名)を実行すると
そのボタンが押下するってだけ。
ただし,同じボタン名がある場合はひとつのボタンのハンドルしか紐付けられない。
(紐付け時にどのボタンのハンドルと紐付けるか選択するようになっている)
え。同じボタン名でも両方それぞれ押下できるようにしろって?
面倒だからやだw
Option Explicit Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal cnm As String, _ ByVal cap As String) As Long Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, _ ByVal lpString As String, _ ByVal cch As Long) As Long Private Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As Long, _ ByVal lpEnumFunc As Long, _ ByVal lParam As Long) As Long Private Declare Function GetWindowRect Lib "user32" (ByVal hWnd As Long, lpRect As RECT) As Long Private Declare Function GetClientRect Lib "user32" (ByVal hWnd As Long, lpRect As RECT) As Long Private Declare Function GetParent Lib "user32" (ByVal hWnd As Long) As Long Private Declare Function IsWindow Lib "user32" (ByVal hWnd As Long) As Long Private Declare Function Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hWnd As Long, _ ByVal wMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long Private Const BUFFER_SIZE As Long = 255 Private Const WM_LBUTTONDOWN = &H201 Private Const WM_LBUTTONUP = &H202 Private Const TARGET_WINDOW_TITLE = "電卓" Private conf As New Collection ' Window サイズ用の型 Type RECT left As Long ' 四角形の左上隅の x 座標を指定します。 top As Long ' 四角形の左上隅の y 座標を指定します。 right As Long ' 四角形の右下隅の x 座標を指定します。 bottom As Long ' 四角形の右下隅の y 座標を指定します。 End Type ' コレクションからオブジェクトを取り出す。存在しない場合はNothingを返す Private Function getObjectFrom(c As Collection, key) As Object Set getObjectFrom = Nothing On Error Resume Next Set getObjectFrom = c.Item(key) On Error GoTo 0 End Function Private Function searchButtons(ByVal hWnd As Long, ByVal buttons As Collection) As Long ' ループを繰り返す場合はTrue searchButtons = True ' クラス名を取得 Dim strClassName As String ' * 255 strClassName = String(255, vbNullChar) GetClassName hWnd, strClassName, Len(strClassName) strClassName = RTrim(left(strClassName, InStr(1, strClassName, vbNullChar) - 1)) If strClassName <> "Button" Then Exit Function End If ' キャプション(ボタン名)を取得 Dim strCaption As String strCaption = String$(BUFFER_SIZE, vbNullChar) ' 予め領域を確保 GetWindowText hWnd, strCaption, Len(strCaption) ' エラーは無視 strCaption = RTrim(left(strCaption, InStr(1, strCaption, vbNullChar) - 1)) ' 末尾のごみを削除 ' ボタンのウィンドウ内の位置取得 Dim p_hWnd As Long p_hWnd = GetParent(hWnd) ' ウィンドウ(親)のハンドル取得。エラー(p_hWnd = NULL)は無視 Dim rc As Long Dim p_lpRect As RECT, lpRect As RECT rc = GetWindowRect(p_hWnd, p_lpRect) ' ウィンドウ(親)の位置取得。エラー(rc = 0)は無視 rc = GetWindowRect(hWnd, lpRect) ' ボタンの位置取得。エラー(rc = 0)は無視 ' ウィンドウ内の位置を求める With lpRect .top = .top - p_lpRect.top .left = .left - p_lpRect.left .bottom = .bottom - p_lpRect.top .right = .right - p_lpRect.left End With ' Debug.Print hWnd, strCaption, lpRect.left, lpRect.top, lpRect.right, lpRect.bottom, rc ' ボタン名ごとにボタンのリストを格納 Dim btn_list As Collection Set btn_list = getObjectFrom(buttons, strCaption) ' ボタンのリスト取得 If btn_list Is Nothing Then ' まだない場合は追加する Set btn_list = New Collection buttons.Add btn_list, strCaption End If ' ボタンの情報 Dim btn As VButton Set btn = New VButton btn.hWnd = hWnd btn.name = strCaption btn.top = lpRect.top btn.left = lpRect.left ' 見つけたボタンをコレクションに追加 btn_list.Add btn End Function Private Sub getButtons(ByRef buttons As Collection) ' クリア Do While buttons.Count > 0 buttons.Remove 1 Loop ' タイトル名からウィンドウを探す Dim hWindow As Long hWindow = FindWindow(vbNullString, TARGET_WINDOW_TITLE) If hWindow = 0 Then Call MsgBox(TARGET_WINDOW_TITLE & "ウィンドウが見つかりません。" & vbLf & _ "起動してから実行してください。") Exit Sub End If ' 対象ウィンドウのボタンを列挙し,使用するボタンのハンドルと名称を取得する Call EnumChildWindows(hWindow, AddressOf searchButtons, ObjPtr(buttons)) End Sub Public Sub pushButton(name As String, Optional useCache As Boolean = False) ' 設定済みであれば,ボタンを押す Dim btn As VButton Set btn = getObjectFrom(conf, name) If btn Is Nothing Then Call MsgBox(name & "ボタンは設定されていません。" & vbLf & _ "設定してから使用してください", vbCritical) Exit Sub End If ' 設定値にあるハンドラを再利用する If useCache Then If IsWindow(btn.hWnd) Then ' ハンドラが有効化? Call SendMessage(btn.hWnd, WM_LBUTTONDOWN, 0, 0) Call SendMessage(btn.hWnd, WM_LBUTTONUP, 0, 0) Exit Sub End If End If ' 設定の位置情報に合致するハンドラを再取得する Dim buttons As Collection ' ボタンの入れ物を用意 Set buttons = New Collection Call getButtons(buttons) ' ボタン情報を再取得 Dim list As Collection Set list = getObjectFrom(buttons, name) If list Is Nothing Then Call MsgBox(name & "ボタンが見つかりませんでした。" & vbLf & _ "アプリが起動していることを確認した上,再設定してください", vbCritical) Exit Sub End If ' 設定の位置情報とマッチするか? Dim isMatch As Boolean isMatch = False Dim obj As Variant For Each obj In list If obj.left = btn.left And obj.top = btn.top Then isMatch = True conf.Remove name conf.Add obj, name Set btn = obj End If Next ' 設定の位置情報とマッチしない??? If Not isMatch Then Call MsgBox("設定または起動しているアプリが不正です。" & vbLf & _ "再度アプリを起動し再設定してください。", vbCritical) Exit Sub End If ' ボタンを押す Call SendMessage(btn.hWnd, WM_LBUTTONDOWN, 0, 0) Call SendMessage(btn.hWnd, WM_LBUTTONUP, 0, 0) End Sub Public Sub SettingButtons() Dim ret As Long ret = MsgBox("ボタンとマクロの対応付けを行ないます。" & vbLf & _ "現在の設定はクリアされます。継続しますか?", vbYesNo + vbQuestion) If ret <> vbYes Then Exit Sub End If Call MsgBox("対象アプリが起動していない場合は起動してください。" & vbLf & _ "OKをクリックすると対象のウィンドウのボタンを押下します。" & vbLf & _ vbLf & _ "期待動作をした場合 ⇒ 「はい」" & vbLf & _ "期待動作をしない場合 ⇒ 「いいえ」" & vbLf & _ "設定を中断する場合 ⇒ 「キャンセル」") ' 設定をクリア Do While conf.Count > 0 conf.Remove 1 Loop Dim buttons As Collection ' ボタンの入れ物を用意 Set buttons = New Collection Call getButtons(buttons) Dim list As Variant For Each list In buttons Dim btn As Variant Dim isSet As Boolean isSet = False Dim name As String name = list(1).name Do For Each btn In list Call SendMessage(btn.hWnd, WM_LBUTTONDOWN, 0, 0) Call SendMessage(btn.hWnd, WM_LBUTTONUP, 0, 0) 'Debug.Print btn.name, btn.hWnd, btn.left, btn.top, IsWindow(btn.hWnd) ret = MsgBox("[" & name & "]ボタンを押下しました。期待動作をしましたか?" & _ vbLf & "しなければ別の[" & name & "]ボタンを押下します。", _ vbYesNoCancel + vbQuestion) If ret = vbYes Then isSet = True conf.Add btn, btn.name Exit For ElseIf ret = vbCancel Then Call MsgBox("設定を中断します。", vbCritical) Exit Sub End If Next If isSet Then Exit Do End If ret = MsgBox("[" & name & "]ボタンは他にありませんでした。" & vbLf & _ "もう一度確認しますか?", vbYesNoCancel + vbQuestion) If ret = vbNo Then Call MsgBox("[" & name & "]ボタンはスキップし,次のボタンを設定します", vbInformation) Exit Do ElseIf ret = vbCancel Then Call MsgBox("設定を中断します。", vbCritical) Exit Sub End If Loop Next Call MsgBox("設定が完了しました") 'dumpConfig End Sub Private Sub dumpConfig() Debug.Print "Dump Config" Dim o As Variant For Each o In conf Debug.Print o.name, o.hWnd, o.left, o.top, IsWindow(o.hWnd) Next End Sub Private Sub listupButtons() Dim buttons As Collection ' ボタンの入れ物を用意 Set buttons = New Collection getButtons buttons Dim list As Variant For Each list In buttons Dim o As Variant For Each o In list Debug.Print o.name, o.hWnd, o.left, o.top, IsWindow(o.hWnd) Next Next End Sub
あ,途中に出てくるVButtonってのはこんなクラスモジュールです。これだけですw
Option Explicit Public hWnd As Long Public name As String Public top As Long Public left As Long Public Sub Class_Initialize() hWnd = 0 name = "" top = 0 left = 0 End Sub
参考: