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

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

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

EXCEL VBAからのWindows.Forms呼出方法

[トピック内 4 記事 (1 - 4 表示)]  << 0 >>

■103689 / inTopicNo.1)  EXCEL VBAからのWindows.Forms呼出方法
  
□投稿者/ PATIO (17回)-(2025/05/23(Fri) 11:53:20)

分類:[Microsoft Office 全般] 

皆様、お久しぶりです。

EXCEL VBAからWindows.Formsのクラスライブラリを操作したいのですが、可能でしょうか?
とある自動化案件で対象のプログラムが.net Frameworkをベース開発した独自GUIライブラリを使用しており、
Win32APIを使ったファンクションでは、情報が拾えないケースが出てきています。
具体的には対象のコントロールのウインドウハンドルを取得しようとしても特定が出来ない。
使用されているコントロールの内、同時実装分の物でWM_GETTEXTが全く効かない為、判別が出来ません。
Accessibility Insights For Windowsでみた所、Nameプロパティで拾えるケースがある事はわかりましたが、
EXCEL VBAから拾う方法がわかりません。
VBA上でWindows.Forms.Formが使えれば、Nameプロパティが拾えるかも考えています。
拾ったウインドウハンドルからForm.FromHandleで紐づけて拾えるのではと思っていますが、
VBAでWidnows.Formsを使用する方法に関しての情報を見つける事が出来ない為、
ご存じの方がいらっしゃいましたらご教示ください。

追加で必要な情報がありましたらできる範囲で対応させていただきます。
以上、よろしくお願いします。
引用返信 編集キー/
■103690 / inTopicNo.2)  Re[1]: EXCEL VBAからのWindows.Forms呼出方法
□投稿者/ 魔界の仮面弁士 (3846回)-(2025/05/23(Fri) 14:09:31)
No103689 (PATIO さん) に返信
> EXCEL VBAからWindows.Formsのクラスライブラリを操作したいのですが、可能でしょうか?
> とある自動化案件で対象のプログラムが.net Frameworkをベース開発した独自GUIライブラリを使用しており、
その .NET Framework 製の「独自GUIライブラリ」というのは、どういったものなのでしょうか。

それが VSTO なのか ActiveX DLL なのか分かりませんが、
そのライブラリ内から System.Windows.Forms.Form を呼んでいるのではないのでしょうか。

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

既存の 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 に頼ることになりそう。


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

> 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
といった感じかと。


> 拾ったウインドウハンドルからForm.FromHandleで紐づけて拾えるのではと思っていますが、
NativeWindows.FromHandle メソッドや Control.FromHandle の話をされているのですか?
引用返信 編集キー/
■103692 / inTopicNo.3)  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プロパティを参照できないかと考えました。

引用返信 編集キー/
■103694 / inTopicNo.4)  Re[3]: EXCEL VBAからのWindows.Forms呼出方法
□投稿者/ 魔界の仮面弁士 (3847回)-(2025/05/26(Mon) 10:44:40)
2025/05/26(Mon) 10:56:33 編集(投稿者)

No103692 (PATIO さん) に返信
> 某社が.net frameworkベースで開発してるGUIライブラリです。
その会社に協力を要請できないのでしょうか。

> そもそもライブラリの仕様すら公開されていません。
dnSpy を使えば、.NET や .NET Framework の EXE/DLL に対してデバッグや実装解析が可能です。
それを許容しない会社の場合、難読化されていて読み解けない可能性もありますが。
https://qiita.com/Tokeiya/items/54fbf30cb21c77c05c41
https://hakase0274.hatenablog.com/entry/2019/09/11/222621

> リストに基づいて処理を進めるようなインターフェイスが無いので
> EXCELマクロを利用してその部分の自動化をしようとしています。
.NET においては、無人操作のために Codeer Friendly を利用している所は見かけます。
https://www.nuget.org/packages?q=Codeer.Friendly

ただ、これは COM インターフェイスは搭載していなかったはずなので、VBA から呼び出したいのであれば、
先述の通り、VBA から呼び出すためのラッパーを C# 等で別途作成する必要があるでしょう。


No103689 (PATIO さん) に返信
> Accessibility Insights For Windowsでみた所、Nameプロパティで拾えるケースがある
ソースコードはこちら。
https://github.com/microsoft/accessibility-insights-windows


No103690 (魔界の仮面弁士) に追記
> Set objIUIAutomationElement = objUIAuto.ElementFromHandle(ByVal hWnd) 'もしくは ElementFromPoint
ElementFromHandle の呼び出しは可能ですが、VBA からだと
ElementFromPoint は直接呼び出せないことを忘れていました…。
ElementFromPoint の引数定義が「ByVal As ユーザー定義型」のため、VBA からは直接呼べません。

COM の低レベルAPI(DispCallFunc 関数)を使えば呼び出せますけれどね。
http://blog.livedoor.jp/tarboh_w-inko/archives/39939041.html

AccessibleObjectFromPoint API の方も「ByVal As ユーザー定義型」な引数ですが、
Declare 宣言を変更することで、同等の呼び出しスタックを再現できるため、
API 版の方が融通が利きます。

UIAutomation の ElementFromPoint メソッドで IUIAutomationElement を得る代わりに
AccessibleObjectFromPoint API でカーソル配下の IAccessible を得るようにしてみたサンプル。

'ThisWorkbook
Option Explicit

Private Declare PtrSafe Function GetCursorPos Lib "user32" (ByVal lpPoint As LongPtr) As Long

#If Win64 Then
Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "Oleacc" (ByVal ptScreen As LongLong, ByRef ppacc As IAccessible, ByRef pvarChild As Variant) As Long
#Else
Private Declare PtrSafe Function AccessibleObjectFromPoint Lib "Oleacc" (ByVal x As Long, ByVal y As Long, ByRef ppacc As IAccessible, ByRef pvarChild As Variant) As Long
#End If

Public Sub ExampleWankuma103689()
  Dim pos(0 To 1) As Long
  Dim endTime As Double
  
  Dim accProp(0 To 2) As Variant
  Dim acc As Office.IAccessible
  Const CHILDID_SELF As Variant = &H0&
  Dim vnt As Variant
  vnt = CHILDID_SELF
  
  '10 秒間繰り返し
  Dim msg As String
  endTime = Timer + 10#
  Do Until endTime <= Timer
    GetCursorPos VarPtr(pos(0))
    msg = Format(endTime - Timer, "0.000") & "秒 (" & CStr(pos(0)) & ", " & CStr(pos(1)) & ")"
    Erase accProp
    On Error Resume Next
#If Win64 Then
    Dim posXY As LongLong
    posXY = pos(1) * &H100000000^ Or CLngLng(pos(0))
    AccessibleObjectFromPoint posXY, acc, vnt
#Else
    AccessibleObjectFromPoint pos(0), pos(1), acc, vnt
#End If
    If Not acc Is Nothing Then
      accProp(0) = Replace(acc.accName, vbNullChar, "")
      accProp(1) = Replace(acc.accValue, vbNullChar, "")
      accProp(2) = Replace(acc.accDescription, vbNullChar, "")
      Set acc = Nothing
      msg = msg & ",Name=[" & accProp(0) & "],Value=[" & accProp(1) & "],Description=[" & accProp(2) & "]"
    End If
    On Error GoTo 0
    [Sheet1!A1].Value = msg
    DoEvents
  Loop
End Sub


ただ、相手が .NET Framework 製のライブラリということで、
上記の IAccessible からでは十分に情報を得られないかも知れません。
UI の操作を行うことが目的なら、IAccessible よりも UI Automation の方が
目的に合致しすいかと。UI Automation の場合、上位のオブジェクトから
And/Or の条件指定で子孫要素を探索できる機構がありますし、
Web 上にも、VBA / .NET いずれに対してもそれなりに情報があると思います。

参考までに、ソースコード付きの VBA サンプルが下記にあります。
https://qiita.com/fenblen_puyo/items/d6d51470a648b9862c07
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ