Q&Aサイトに下記質問がありました。
(この質問も何となく似たような感じの内容です。)
- Excel, VBA, UI Automation を使用して、コンボボックス内の選択肢をすべてを取得
- https://teratail.com/questions/93907
上記サイトのアカウントは持っていないので回答を付けることはできませんが、私のブログ記事を参照していただいているようなので、この場でサンプルコードを載せてみようかと思います。
例として、メモ帳のフォントダイアログにある「文字セット」コンボボックスの内容の取得を試されているので、同じ処理のマクロを書いてみます。
※下記コードはエラー処理していませんので、場合によってはループが止まらなくなる可能性があります。
'32ビット版Officeを対象 Option Explicit Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, ByVal hWndChildAfter As Long, ByVal lpszClass As String, ByVal lpszWindow As String) 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 WM_CLOSE = &H10 Private Const WM_COMMAND As Long = &H111 Public Sub Sample() 'メモ帳のフォントダイアログにある「文字セット」コンボボックスの内容取得 Dim uiAuto As CUIAutomation Dim elmFontDlg As IUIAutomationElement Dim elmCharSetCbo As IUIAutomationElement Dim elmCharSetLCbo As IUIAutomationElement Dim ecptn As IUIAutomationExpandCollapsePattern Dim cndListItems As IUIAutomationCondition Dim aryListItems As IUIAutomationElementArray Dim hNotepad As Long Dim hFontDlg As Long Dim i As Long Set uiAuto = New CUIAutomation 'メモ帳取得 Shell "notepad.exe", vbNormalFocus Do hNotepad = FindWindowEx(0, 0, "Notepad", vbNullString) DoEvents Loop Until hNotepad <> 0 'フォントダイアログ取得 PostMessage hNotepad, WM_COMMAND, &H21, 0 'フォントダイアログ表示 Do hFontDlg = FindWindowEx(0, 0, "#32770", "フォント") DoEvents Loop Until hFontDlg <> 0 Set elmFontDlg = uiAuto.ElementFromHandle(ByVal hFontDlg) If elmFontDlg Is Nothing Then Exit Sub '文字セットコンボボックス取得→オープン Do Set elmCharSetCbo = GetElement(uiAuto, _ elmFontDlg, _ UIA_AccessKeyPropertyId, _ "Alt+r", _ UIA_ComboBoxControlTypeId) DoEvents Loop While elmCharSetCbo Is Nothing Set ecptn = elmCharSetCbo.GetCurrentPattern(UIA_ExpandCollapsePatternId) ecptn.Expand '文字セットコンボボックス内容列挙→クローズ Do Set elmCharSetLCbo = GetElement(uiAuto, _ elmCharSetCbo, _ UIA_ClassNamePropertyId, _ "ComboLBox") DoEvents Loop While elmCharSetLCbo Is Nothing Set cndListItems = uiAuto.CreatePropertyCondition( _ UIA_ControlTypePropertyId, _ UIA_ListItemControlTypeId _ ) Set aryListItems = elmCharSetLCbo.FindAll( _ TreeScope_Subtree, _ cndListItems _ ) For i = 0 To aryListItems.Length - 1 Debug.Print aryListItems.GetElement(i).CurrentName Next ecptn.Collapse 'フォントダイアログを閉じてメモ帳終了 PostMessage hFontDlg, WM_CLOSE, 0, 0 PostMessage hNotepad, WM_CLOSE, 0, 0 End Sub Private Function GetElement(ByVal uiAuto As CUIAutomation, _ ByVal elmParent As IUIAutomationElement, _ ByVal propertyId As Long, _ ByVal propertyValue As Variant, _ Optional ByVal ctrlType As Long = 0) As IUIAutomationElement Dim cndFirst As IUIAutomationCondition Dim cndSecond As IUIAutomationCondition Set cndFirst = uiAuto.CreatePropertyCondition( _ propertyId, _ propertyValue _ ) If ctrlType <> 0 Then Set cndSecond = uiAuto.CreatePropertyCondition( _ UIA_ControlTypePropertyId, _ ctrlType _ ) Set cndFirst = uiAuto.CreateAndCondition( _ cndFirst, _ cndSecond _ ) End If Set GetElement = elmParent.FindFirst(TreeScope_Subtree, cndFirst) End Function
質問されている方はUIAutomation→IAccessibleでの処理を行われていますが、コンボボックスを一度展開すれば、UIAutomationでも内容を取得することができました。
あるいは、IAccessibleだけを使って、下記のように処理しても良いかもしれません。
'32ビット版Officeを対象 Option Explicit Private Declare Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As IAccessible, ByVal iChildStart As Long, ByVal cChildren As Long, ByRef rgvarChildren As Any, ByRef pcObtained As Long) As Long Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, ByVal dwId As Long, riid As Any, ByRef ppvObject As IAccessible) As Long Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As Long, ByVal hWndChildAfter As Long, ByVal lpszClass As String, ByVal lpszWindow As String) As Long Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, lpiid As Any) 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 CHILDID_SELF = &H0& Private Const OBJID_CLIENT = &HFFFFFFFC Private Const ROLE_SYSTEM_LIST = &H21 Private Const WM_CLOSE = &H10 Private Const WM_COMMAND As Long = &H111 Public Sub Sample2() 'メモ帳のフォントダイアログにある「文字セット」コンボボックスの内容取得 Dim hNotepad As Long Dim hFontDlg As Long Dim accFontDlg As IAccessible Dim accCharSetLst As IAccessible Dim IID(0 To 3) As Long Dim i As Long 'メモ帳取得 Shell "notepad.exe", vbNormalFocus Do hNotepad = FindWindowEx(0, 0, "Notepad", vbNullString) DoEvents Loop Until hNotepad <> 0 'フォントダイアログ取得 PostMessage hNotepad, WM_COMMAND, &H21, 0 'フォントダイアログ表示 Do hFontDlg = FindWindowEx(0, 0, "#32770", "フォント") DoEvents Loop Until hFontDlg <> 0 IIDFromString StrPtr("{618736E0-3C3D-11CF-810C-00AA00389B71}"), IID(0) 'IID_IAccessible AccessibleObjectFromWindow hFontDlg, OBJID_CLIENT, IID(0), accFontDlg If accFontDlg Is Nothing Then Exit Sub 'コンボボックス取得→内容列挙 Set accCharSetLst = GetAccessibleObject(accFontDlg, _ "文字セット(R):", _ ROLE_SYSTEM_LIST) If accCharSetLst Is Nothing Then Exit Sub For i = 1 To accCharSetLst.accChildCount Debug.Print accCharSetLst.accName(i&) Next 'フォントダイアログを閉じてメモ帳終了 PostMessage hFontDlg, WM_CLOSE, 0, 0 PostMessage hNotepad, WM_CLOSE, 0, 0 End Sub Private Function GetAccessibleObject(ByVal SrcAccObj As IAccessible, _ ByVal TgtAccName As String, _ ByVal TgtAccRole As Long) As IAccessible Dim ret As IAccessible Dim list() As Variant Dim cnt As Long, i As Long Set ret = Nothing '初期化 If (SrcAccObj.accName(CHILDID_SELF) = TgtAccName) And _ (SrcAccObj.accRole(CHILDID_SELF) = TgtAccRole) Then Set ret = SrcAccObj Else cnt = SrcAccObj.accChildCount If cnt > 0 Then ReDim list(cnt - 1) If AccessibleChildren(SrcAccObj, 0, cnt, list(0), cnt) = 0 Then For i = LBound(list) To UBound(list) If TypeOf list(i) Is IAccessible Then Set ret = GetAccessibleObject(list(i), TgtAccName, TgtAccRole) If Not ret Is Nothing Then Exit For End If Next End If End If End If Set GetAccessibleObject = ret End Function
いずれにしても、ここで挙げたコードはほんの一例ですので、対象となるアプリケーションが違えば処理も異なってきます。
大切なのは、Inspect等のツールを使って、対象となる要素がどのような構造になっているのかをよく見ることだと思います。
また、外部アプリケーションの操作をVBAマクロでやろうとすると、処理が複雑になることが多いので、“VBAにそんな無理はさせない!手動でできるところは手動でやる!!”というように、処理を切り分けることも考えてみてはいかがでしょうか。
teratail で質問した本人です。
お礼遅くなりまして、申し訳ありません。
おかげさまで、実施したい事ができました。
ありがとうございました。
別件で、PostMessage について、質問させてください。
>PostMessage hNotepad, WM_COMMAND, &H21, 0 ‘フォントダイアログ表示
きぬあささんは、メニューアイテム、コントロール、アクセラレーターの ID は、どの様に取得されたか、教えて頂けないでしょうか。
PostMessage を使用したことがなく、ネットで調べたところ、&H21 で、フォントダイアログを起動している様なのがわかり、
Resource Hacker と言うツールで取得できると、別のサイトで見かけ、そこで説明されていた様に notepad.exe を開いてみたのですが、MENUがなく取得できませんでした。
> nux5様
無事に解決されたとのことで何よりです。
> メニューアイテム、コントロール、アクセラレーターの ID は、どの様に取得されたか、教えて頂けないでしょうか。
今回の場合のように特定のアプリケーション(ウィンドウ)に対してどのようなメッセージが発行されたかを調べる場合、私は「Spy++」を使用しています。
・Spy++ の概要
https://msdn.microsoft.com/ja-jp/library/dd460756.aspx
Visual Studio付属のツールですが、Internet Archiveかどこかからダウンロードできたような…。
(リンク先は忘れてしまいました。すみません。)
> Resource Hacker と言うツールで取得できる
Resource Hackerは文字通り、実行ファイルやDLLファイル等のリソースを見たり修正したりするソフトですので、今回のようなアプリケーションの動作を調べる場合には適していないツールです。
実行ファイルが持っているアイコンやダイアログを調べる際には便利なツールですので、そういった機会があればお使いください。
きぬあささん、
探してみます。
ありがとうございました。