tagCANDY CGI VBレスキュー(花ちゃん)の Visual Basic 6.0用 掲示板 [ツリー表示へ]   [Home]
一括表示(VB6.0)
タイトルメニューハンドルの取得方法
記事No15685
投稿日: 2013/06/01(Sat) 01:02
投稿者genesis
はじめまして。お世話になります。


他アプリのメニューバーのメニューハンドルを取得するよい方法が
見いだせず困っております。

そのメニューバーのウィンドウハンドルhwndmnbは、spy++等で
得られていますが、GetMenu(hwndmnb)では0が戻ってきてしまい
メニューハンドルが取得できません。

今は、
・GetSubMenu(hmenu,0)<>0
かつ
・GetMenuItemCount(hmenu)=既知のメニューバーのメニュー項目数
かつ
・GetMenuItemInfo(hmenu, i, 1, lpmii) でi番目のメニュー項目の文字列
 が、既知のメニューバー中のものと一致(すべてのi s.t. {0<=i<=メニュー項目数-1}に対し)
ようなhmenuを、hmenu=1,2,3,・・・と順々に調べていくという力任せな
方法をとっています。
幸いhmenuがなんとか特定できていますが、hmenuは常に一定ではなく、
またlong型の最大値まで調べることとなると特定に時間を要する可能性もあり、
よりよい方法がないか、識者の皆様のアドバイスを賜りたいと存じます。
(浅知恵ですが、プロセスとかスレッドが保有するメニューを知るような方法
が無いか、とか…)


なお、spy++の調査で、以下はわかっております。
・そのメニューバーのj番目の項目をクリックしプルダウンメニューを出して
 そこの項目を適当に選ぶと、WM_MENUSELECTメッセージを検知でき、
 それのパラメータから、サブメニューhsubmenuが得られる。
・このサブメニューは、hsubmenu=GetSubMenu(hmenu,j)を満たす。
 ただしhsubmenuからhmenuを逆引きする術がない(しらみつぶし法しか
 ない…)
・上記で項目をクリックしても、WM_COMMANDメッセージが検知されない
 (このアプリが独自のメニュー実装になっているからか・・・)


OS:WindowsXP
VB:VB6


なにとぞよろしくお願いいたします。

[ツリー表示へ]
タイトルRe: メニューハンドルの取得方法
記事No15686
投稿日: 2013/06/01(Sat) 15:35
投稿者VBレスキュー(花ちゃん)
色々書いて頂いても中途半端な書き方ではまったく理解できません。

> 得られていますが、GetMenu(hwndmnb)では0が戻ってきてしまい
> メニューハンドルが取得できません。

GetMenu(hwndmnb) の引数は正しいのですか? 何を指定しているのですか?
この辺から詳しく説明しないと。

メモ帳のメニューなら取得できるのですか?
使用しているコードに間違いがないのですか?
対象のメニューが特殊なものですか、それで取得できないというならその対象のメニューを
明記するなり、同様のメニューで説明するなりしないとこれを見ている人には説明のしようが
ないかと思うのですが。

コードが間違っているのなら、現在使っているコードを掲載するとか?
又、メニューハンドルの取得方法といっておられますが、ハンドルの取得が目的ではないはず
ハンドルを取得してどうしたいのかを含め説明して頂かないと投稿された内容では、判断材料に
なりません。
spy++ でハンドルがとれているのなら取れるはずですが。 位の事しか答えられないでは。

[ツリー表示へ]
タイトルRe: メニューハンドルの取得方法
記事No15687
投稿日: 2013/06/01(Sat) 21:59
投稿者魔界の仮面弁士
> OS:WindowsXP
ということは、権限周りの問題や、64bitアプリとの通信問題は
除外して考えてよさそうですね。


> そのメニューバーのウィンドウハンドルhwndmnbは、spy++等で
> 得られていますが、GetMenu(hwndmnb)では0が戻ってきてしまい
> メニューハンドルが取得できません。
Spy++ で得た HWND を GetMenu に渡している…ということでしょうか。
(取得したいのは、ポップアップメニューではないのですよね?)

Option Explicit
Private Declare Function GetMenu Lib "user32" (ByVal hWnd As OLE_HANDLE) As OLE_HANDLE
Private Sub Command1_Click()
    Dim target As OLE_HANDLE
    target = CLng(Text1.Text)   'Spy++で調べたウィンドウハンドルを入れておく
    Text2.Text = CStr(GetMenu(target))
    Text3.Text = CStr(Err.LastDllError)
End Sub


戻り値に 0 が返却されたときに、Err.LastDllError は何を返しますか?

On Error Resume Next 等で、エラーを握りつぶしており、
実際には別の場所に問題を抱えていた…ということはありませんか?


> (浅知恵ですが、プロセスとかスレッドが保有するメニューを知るような方法
> が無いか、とか…)
メニューはウィンドウに所属するので、本来はGetMenu が最適でしょうね。


>  (このアプリが独自のメニュー実装になっているからか・・・)
そのアプリが、別ウィンドウに所属するメニューを、
ポップアップメニューとして表示しているとか…?

[ツリー表示へ]
タイトルRe^2: メニューハンドルの取得方法
記事No15688
投稿日: 2013/06/04(Tue) 12:04
投稿者genesis
VBレスキューさま、魔界の仮面弁士さま、
早速のご返事をありがとうございます。言葉足らずの点が多く、
申し訳ございません。

ターゲットはIE6なのですが、そのウィンドウの中で別の
アプリケーションが呼び出されて表示されています(一種の
リッチクライアントソフトウェアか)。
その中に項目数3のメニューバー(IEのメニューバーとは別物)
があって、項目を押すとプルダウンメニューが展開されること
から、メニュー操作を実現したいと考えております。その
メニューはショートカットキーが無く、座標を与えてマウスで
クリックすることが必要となるため、GetMenuItemRect()を
使おうと考えております。
なお上記のメニューバーは、spy++で、所定のウィンドウハンドル
(hwndmnb)によりハイライトさせることができております。


以下コードを示します。メモ帳では所望の結果が得られた
のですが、ターゲットに対してはメニューハンドルが0(エラー
番号は0)で、ターゲットのメニューバーのメニューハンドルが
GetMenu()では取得できていないと推測しております。



Sub func()

Dim classname(0 To 6) As String, i As Integer
Dim wtitle As String, hwnd As Long, hmenu As Long, hsubmenu0 As Long
Dim str As String, mcount As Long

classname(0) = "IEFrame"
classname(1) = "Shell DocObject View"
classname(2) = "Internet Explorer_Server"   '第2階層hwndは1つ
classname(3) = vbNullString     '可変のため。第3階層hwndは1つ
classname(4) = vbNullString     '可変のため。第4階層hwndは1つ
classname(5) = "MnbWin"                     '第5階層hwndは1つ

wtitle = InputBox("ウィンドウタイトル名を入力")

hwnd = 0
If InStr(wtitle, " - Microsoft Internet Explorer") > 0 Then 'ターゲットの場合
    For i = 0 To 5 Step 1
        If i = 0 Then
            hwnd = FindWindowEx(hwnd, 0, classname(i), wtitle)
        Else    'i=5の時のhwndがhwndmnbに相当。
            hwnd = FindWindowEx(hwnd, 0, classname(i), vbNullString)
        End If
        If hwnd <> 0 Then
            hmenu = GetMenu(hwnd)
            If hmenu = 0 Then
                str = "Error No:" & CStr(Err.LastDllError)
            Else
                mcount = GetMenuItemCount(hmenu)
                str = "HMENU=" & Hex(hmenu) & " 項目数=" & mcount
            End If
            hsubmenu0 = GetSubMenu(hmenu, 0)    '0番目のサブメニュー
            If hsubmenu0 = 0 Then
                str = str & vbCrLf & "Error No:" & CStr(Err.LastDllError)
            Else
                mcount = GetMenuItemCount(hsubmenu0)
                str = str & vbCrLf & "HSUBMENU0=" & Hex(hsubmenu0) & " 項目数=" & mcount
            End If
            MsgBox i & " hwnd=" & Hex(hwnd) & vbCrLf & str
        End If
    Next i
ElseIf InStr(wtitle, " - メモ帳") > 0 Then
    hwnd = FindWindowEx(hwnd, 0, vbNullString, wtitle)
    If hwnd > 0 Then
        hmenu = GetMenu(hwnd)
        If hmenu = 0 Then
            str = "Error No:" & CStr(Err.LastDllError)
        Else
            mcount = GetMenuItemCount(hmenu)
            str = "HMENU=" & Hex(hmenu) & " 項目数=" & mcount
            str = str & MyGetMenuItemString(hmenu, 3) 'メニューバーの3番目の項目名
        End If
        hsubmenu0 = GetSubMenu(hmenu, 0)    '0番目のサブメニュー
        If hsubmenu0 = 0 Then
            str = str & vbCrLf & "Error No:" & CStr(Err.LastDllError)
        Else
             mcount = GetMenuItemCount(hsubmenu0)
             str = str & vbCrLf & "HSUBMENU0=" & Hex(hsubmenu0) & " 項目数=" & mcount
             str = str & MyGetMenuItemString(hsubmenu0, 8)  'メニューバーの0番目のサブメニューの8番目の項目名
        End If
        MsgBox i & " hwnd=" & Hex(hwnd) & vbCrLf & str
    End If
End If

End Sub

''※MyGetMenuItemString()は、自作関数でGetMenuItemInfo()を用いている

上記による調査結果
i=0(第0階層) HEMNU取得OKだが項目数=0。それの0番目SUBMENU取得NGでエラー番号0
i=1(第1階層) HMENU取得NGでエラー番号0。それの〃NGでエラー番号1401
i=2(第2階層) HMENU取得NGでエラー番号0。それの〃NGでエラー番号1401
i=3(第3階層) HEMNU取得OKだが項目数=-1。それの〃NGでエラー番号1401
i=4(第4階層) HMENU取得NGでエラー番号0。それの〃NGでエラー番号1401
i=5(第5階層) HMENU取得NGでエラー番号0。それの〃NGでエラー番号1401
※第5階層がメニューバーに相当。


やはりメニューバー風の「見せかけ」で、独自の実装なのでしょうか…

> > OS:WindowsXP
> ということは、権限周りの問題や、64bitアプリとの通信問題は
> 除外して考えてよさそうですね。
>
>
> > そのメニューバーのウィンドウハンドルhwndmnbは、spy++等で
> > 得られていますが、GetMenu(hwndmnb)では0が戻ってきてしまい
> > メニューハンドルが取得できません。
> Spy++ で得た HWND を GetMenu に渡している…ということでしょうか。
> (取得したいのは、ポップアップメニューではないのですよね?)
>
> Option Explicit
> Private Declare Function GetMenu Lib "user32" (ByVal hWnd As OLE_HANDLE) As OLE_HANDLE
> Private Sub Command1_Click()
>     Dim target As OLE_HANDLE
>     target = CLng(Text1.Text)   'Spy++で調べたウィンドウハンドルを入れておく
>     Text2.Text = CStr(GetMenu(target))
>     Text3.Text = CStr(Err.LastDllError)
> End Sub
>
>
> 戻り値に 0 が返却されたときに、Err.LastDllError は何を返しますか?
>
> On Error Resume Next 等で、エラーを握りつぶしており、
> 実際には別の場所に問題を抱えていた…ということはありませんか?
>
>
> > (浅知恵ですが、プロセスとかスレッドが保有するメニューを知るような方法
> > が無いか、とか…)
> メニューはウィンドウに所属するので、本来はGetMenu が最適でしょうね。
>
>
> >  (このアプリが独自のメニュー実装になっているからか・・・)
> そのアプリが、別ウィンドウに所属するメニューを、
> ポップアップメニューとして表示しているとか…?

[ツリー表示へ]
タイトルRe^3: メニューハンドルの取得方法
記事No15689
投稿日: 2013/06/05(Wed) 17:31
投稿者魔界の仮面弁士
ポップアップメニューの場合、GetMenu では拾えません。
ウィンドウに関連付けられているメニューではないからです。

必要なのは親メニューなのでしょうが…使う側としても、
LoadMenu や CreateMenu / CreatePopupMenuEx を通じて
得るハンドルであって、hWnd は無関係なんですよね。


> 座標を与えてマウスでクリックすることが必要となるため、
AccessibleObjectFromWindow API か AccessibleObjectFromPoint API を使って
親ウィンドウの IAccessible インターフェイスを得て、そこから
目的のメニュー項目まで、AccessibleChildren API か
accNavigate / accChild などで辿ることはできるでしょうか?

そこまで辿れれば、IAccessible.doDefaultAction メソッドが使えそうです。

IAccessible の調査には、accExplorer や Inspect ツールを使えます。
http://www.ka-net.org/blog/?p=1131

[ツリー表示へ]
タイトルRe^4: (御礼・一応解決)メニューハンドルの取得方法
記事No15690
投稿日: 2013/06/08(Sat) 09:44
投稿者genesis
魔界の仮面弁士さま、引き続く数々のご助言をありがとうございます。


まず結論から言うと、「メニューはウィンドウに属する」を足掛かりに
いろいろ調べた末、一応解決を見ました。

全プロセスの全ウィンドウハンドルに対してGetMenu()等を行った所、
今回ターゲットとするIEウィンドウのタイトルと同名(但し文字列
" - Microsoft Internet Explorer"を除く)の不可視ウィンドウ(の
トップウィンドウハンドル)に対するGetMenu()により、所望の
メニューバーのメニューハンドルが取得できることが判りました。
このメニューハンドルを用いてGetMenuItemInfo()、GetSubMenu()を
行い、ターゲットのメニューと特定できました。

隠しウィンドウ自体はメニューバーを持つだけでメニュークリック
に反応こそすれサブメニューすら展開されないのですが、
ターゲットのメニューにかかわる情報は、この「隠しメニュー」から
とれるような仕掛けになっているみたいです。


あらためて、皆様に御礼申し上げます。



#AccessibleObjectにかかわる情報もありがとうございます。
 今回は出番がなかったようですが、他アプリ操作を自動化
 する手段の一つとして勉強させていただきます。
 (accExplorerを手に入れて少しトライしたのですが、正直
 まだ理解が及ばず、今回ご報告できる内容に達しており
 ません…)
 ウィンドウに関連付けられていないポップアップメニュー
 のような存在もはじめて知りました。メニューハンドル
 取得で行き詰った際、選択肢の1つとして頭に入れておき
 たいと思います。


> ポップアップメニューの場合、GetMenu では拾えません。
> ウィンドウに関連付けられているメニューではないからです。
>
> 必要なのは親メニューなのでしょうが…使う側としても、
> LoadMenu や CreateMenu / CreatePopupMenuEx を通じて
> 得るハンドルであって、hWnd は無関係なんですよね。
>
>
> > 座標を与えてマウスでクリックすることが必要となるため、
> AccessibleObjectFromWindow API か AccessibleObjectFromPoint API を使って
> 親ウィンドウの IAccessible インターフェイスを得て、そこから
> 目的のメニュー項目まで、AccessibleChildren API か
> accNavigate / accChild などで辿ることはできるでしょうか?
>
> そこまで辿れれば、IAccessible.doDefaultAction メソッドが使えそうです。
>
> IAccessible の調査には、accExplorer や Inspect ツールを使えます。
> http://www.ka-net.org/blog/?p=1131

[ツリー表示へ]