C# と VB.NET の質問掲示板

わんくま同盟

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト


■103692 / )  Re[2]: EXCEL VBAからのWindows.Forms呼出方法
□投稿者/ PATIO (18回)-(2025/05/26(Mon) 09:30:21)
魔界の仮面弁士 さん、お久しぶりです。
色々有用な情報をいただけたのでインラインで返信します。

No103690 (魔界の仮面弁士 さん) に返信
> ■No103689 (PATIO さん) に返信
>>EXCEL VBAからWindows.Formsのクラスライブラリを操作したいのですが、可能でしょうか?
>>とある自動化案件で対象のプログラムが.net Frameworkをベース開発した独自GUIライブラリを使用しており、
> その .NET Framework 製の「独自GUIライブラリ」というのは、どういったものなのでしょうか。
>
> それが VSTO なのか ActiveX DLL なのか分かりませんが、
> そのライブラリ内から System.Windows.Forms.Form を呼んでいるのではないのでしょうか。

某社が.net frameworkベースで開発してるGUIライブラリです。
標準のGUIの見た目が気に入らないので色々装飾を付けたりする為にラップしているイメージです。
例で言うとタイトルバーのバックグラウンドの塗潰しにグラデーションを付けたいとかそういう類の物です。
ボタンの背景色を変えたいとか。
問題は、この辺の実装をする為に従来のWindowsの処理の流れを全く無視した形で実装しているので
Win32APIで情報を取得しようとしても取得できない。設定しようとしても設定できない状態にある事です。


>>Win32APIを使ったファンクションでは、情報が拾えないケースが出てきています。
> 何に対して、どんな情報を拾おうとしているのでしょうか?
> 相手が System.Windows.Forms 名前空間のコントロールなのであれば、
> その DLL 自体に操作用のメソッドを追加するのが手っ取り早いと思うのですが…。

他社が開発した物なので勝手にインターフェイスを追加する事も出来ませんし、
そもそもライブラリの仕様すら公開されていません。
見えている範囲で外部のアプリケーションから操作しようとしています。
具体的には作ってもらったアプリは人間が操作して使う事しか想定されておらず、
リストに基づいて処理を進めるようなインターフェイスが無いので
EXCELマクロを利用してその部分の自動化をしようとしています。


> 既存の DLL に手を入れらないまま外部操作を試みているなら、
> Win32、WinForms、WPF を外部操作可能な「Codeer Friendly」で操作できるか試してみるとか。
> https://ishikawa-tatsuya.hatenablog.com/entry/2014/12/12/011333
> https://github.com/Codeer-Software/Friendly/blob/master/README.jp.md
> https://qiita.com/murasuke/items/936c4e12af314777e7b2
> VBA からの操作が必要となると、Codeer Friendly をカプセル化する必要があるかもしれないので
> 今回の要件を満たすかどうかは何とも言えませんが。

これは見つけられていませんでした。
可能性があるのであれば、探して調査してみます。

>>具体的には対象のコントロールのウインドウハンドルを取得しようとしても特定が出来ない。
> その「対象のコントロール」というのが、どこにある何者なのかも不明ですし、
> どうやって hWnd を取得しようとしているのかについての言及も無いので、
> すべて憶測でしか答えられませんが、もしもウィンドウハンドルが得られないのだとすれば、
> そもそもウィンドウレスコントロールという可能性もありますね。
> その場合は、UI Automation か MSAA に頼ることになりそう。

さすがにウインドウレスというわけではないです。
一応、SPY++で参照は出来ますので。
但し、コントロールIDはパーマネントではなく、ウインドウハンドルが突っ込まれているようで
コントロールIDも使えませんし、ベースのウインドウにタイトルが設定されていない状態なので
対象のウインドウを狙い撃ちでウインドウハンドルを取得できません。
よくやる方法としてベースのウインドウのハンドルに関してはタイトルを使って判別し、
状況によっては特定のコントロールを子供に持っているかを使っています。

>>使用されているコントロールの内、同時実装分の物でWM_GETTEXTが全く効かない為、判別が出来ません。
> ウィンドウハンドルは取得できるが、WM_GETTEXT で情報が得られかったという話をしているのでしょうか。
> ウィンドウハンドルがあるからといって、WM_GETTEXT で情報が得られる保証は無いですよね。
> それはターゲットのウィンドウ次第なわけで。

その通りです。
ターゲットウインドウが通常のウインドウの構成と全く異なる為に取得できない状態にあります。
そもそもコントロールIDが使えない時点で怪しかったんですが、確認してみたら案の定という感じです。

>>Accessibility Insights For Windowsでみた所、Nameプロパティで拾えるケースがある事はわかりましたが、
>>EXCEL VBAから拾う方法がわかりません。
> VBA からであれば、MSAA の IAccessible インターフェイスで取得できると思います。
> VBA からだと、IAccessible の accName プロパティかな。
>
> IAccessible を得るには、
>  方法1) 座標が分かっている場合は、AccessibleObjectFromPoint API
>  方法2) hWnd が分かっている場合は、AccessibleObjectFromWindow API
>  方法3) hWnd が無い場合は、ウィンドウを持つ祖先オブジェクトから子孫要素を辿る
> といった方法があります。
> 昔は、OLEACC.DLL を参照設定する必要がありましたが、
> 最近の Excel なら、標準で Dim objAcc As Office.IAccessible と書けるので、そっちでも良いかも。
>  'Call AccessibleObjectFromPoint32(x, y, objAcc, vnt)
>  'Call AccessibleObjectFromPoint64(y * &H100000000^ Or x, objAcc, vnt)
>  Call AccessibleObjectFromWindow(ByVal hWnd, OBJID_NATIVEOM, guidIDispatch, objAcc)
>  Debug.Print objAcc.accName
>
> ※AccessibleObjectFromPoint の第一引数は POINT 構造体を「値渡し」する仕様なので、
>  32bit VBA から呼ぶ場合は、第一引数を X 引数と Y 引数に分割したり、
>  64bit VBA から呼ぶ場合は、X と Y を単一の 64bit 整数型にまとめてから値渡しするなどの工夫が必要。
>
>
> あるいは、MSAA の後継たる UI Automation 経由でも得られるかと。
> 参照設定で UIAutomationClient を加えておいたうえで、
> UIAutomationClient クラスの ElementFromHandle メソッドか ElementFromPoint メソッドを使うと、
> 操作対象の IUIAutomationElement を得られるので、そこから
>  Dim objUIAuto As UIAutomationClient.CUIAutomation
>  Set objUIAuto = New UIAutomationClient.CUIAutomation
>  Dim objIUIAutomationElement As UIAutomationClient.IUIAutomationElement
>  Set objIUIAutomationElement = objUIAuto.ElementFromHandle(ByVal hWnd) 'もしくは ElementFromPoint
>  Dim objlegacy As UIAutomationClient.IUIAutomationLegacyIAccessiblePattern
>  Set objlegacy = objIUIAutomationElement.GetCurrentPattern(UIA_LegacyIAccessiblePatternId)
>  Debug.Print legacy.CurrentName
> といった感じかと。

有用な情報ありがとうございます。
糸口になりそうなのでこちらも調べてみます。
社内で作成している業務用のツールに関しては見た目に凝ったりしないので
素直にMicrosoftのライブラリを使用しているのでWin32APIで処理できる事がほとんどなのですが、
外部で開発されたアプリに関しては見た目を飾る為に通常の実装とは異なる実装をされているケースがあり、
自動化を行う場合の障害になっています。
自動化の要件が結構複雑で単なる固定の項目を選択すれば済むというような物ではないので
画面のコントロールから情報を拾う必要があるんですが、これがほとんどできない状態なので
うまく行かない状況なのです。


>>拾ったウインドウハンドルからForm.FromHandleで紐づけて拾えるのではと思っていますが、
> NativeWindows.FromHandle メソッドや Control.FromHandle の話をされているのですか?

イメージとしてはControl.FromHandleです。
これでウインドウハンドルからFormに紐づけられれば、Nameプロパティを参照できないかと考えました。

返信 編集キー/


管理者用

- Child Tree -