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

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

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

別アプリのリストボックス上のフォーカス行をWクリックしたい

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

■94621 / inTopicNo.1)  別アプリのリストボックス上のフォーカス行をWクリックしたい
  
□投稿者/ Tom (14回)-(2020/04/25(Sat) 10:18:57)

分類:[C#] 

開発環境:VisualStudio 2017
使用言語:C#

別アプリを制御しようとしています。
やりたいことは別アプリ上のリストボックスのフォーカス行をダブルクリックなのですが、
当然ながらマウスポインタは全然別の場所に居ます。

また、別アプリのフォーカス行はリストボックスの可視位置に居るのですが、
そのリストボックスがマウスカーソルがそこまで届かない場所(デスクトップ描画外)にいる場合はありえます。

そもそもこんなことが可能なのでしょうか?
方法すら皆目思いつきません。

賢者の方、アドバイスを頂けませんでしょうか?
引用返信 編集キー/
■94625 / inTopicNo.2)  Re[1]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ とっちゃん (673回)-(2020/04/25(Sat) 15:19:53)
No94621 (Tom さん) に返信
> 開発環境:VisualStudio 2017
> 使用言語:C#
>
> 別アプリを制御しようとしています。
> やりたいことは別アプリ上のリストボックスのフォーカス行をダブルクリックなのですが、
> 当然ながらマウスポインタは全然別の場所に居ます。
>
> また、別アプリのフォーカス行はリストボックスの可視位置に居るのですが、
> そのリストボックスがマウスカーソルがそこまで届かない場所(デスクトップ描画外)にいる場合はありえます。
>
> そもそもこんなことが可能なのでしょうか?
> 方法すら皆目思いつきません。

LVN_DBLCLK かな?制御したいアプリが WinForms か Win32 Nativeならこれで行けると思います。

https://docs.microsoft.com/en-us/windows/win32/controls/lbn-dblclk

でも System.Windows.Forms.ListBox にはイベントないな。。。


引用返信 編集キー/
■94630 / inTopicNo.3)  Re[2]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Tom (15回)-(2020/04/26(Sun) 12:07:17)
とっちゃん さま

アドバイス、ありがとうございます。
>LVN_DBLCLK かな?制御したいアプリが WinForms か Win32 Nativeならこれで行けると思います。
>
> https://docs.microsoft.com/en-us/windows/win32/controls/lbn-dblclk
>
> でも System.Windows.Forms.ListBox にはイベントないな。。。
情けないことにちょっとまだ上記内容がわかっていないのですが、
よくよく考えたら ダブルクリックのSendMessageをあるハンドルに対して行う って
単純なことで良いのでしょうか?
あるハンドル というのが自アプリ内のものではなく、別アプリのものであっても問題ないと。

もしそれであったらと思い、いろいろ調べたのですが、そもそも「ダブルクリックのSendMessage」を
どうやって作ればよいのかがわかりません。

上記内容、わたくしが勘違いしていたらそのご指摘と、
実現するために更なるアドバイスを頂けませんでしょうか?

引用返信 編集キー/
■94631 / inTopicNo.4)  Re[3]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ とっちゃん (674回)-(2020/04/26(Sun) 14:20:37)
No94630 (Tom さん) に返信
> とっちゃん さま
>
> アドバイス、ありがとうございます。
> >LVN_DBLCLK かな?制御したいアプリが WinForms か Win32 Nativeならこれで行けると思います。
>>
>>https://docs.microsoft.com/en-us/windows/win32/controls/lbn-dblclk
>>
>>でも System.Windows.Forms.ListBox にはイベントないな。。。
> 情けないことにちょっとまだ上記内容がわかっていないのですが、
> よくよく考えたら ダブルクリックのSendMessageをあるハンドルに対して行う って
> 単純なことで良いのでしょうか?
> あるハンドル というのが自アプリ内のものではなく、別アプリのものであっても問題ないと。
>
> もしそれであったらと思い、いろいろ調べたのですが、そもそも「ダブルクリックのSendMessage」を
> どうやって作ればよいのかがわかりません。
>
> 上記内容、わたくしが勘違いしていたらそのご指摘と、
> 実現するために更なるアドバイスを頂けませんでしょうか?
>
LVN_DBLCLK というメッセージは、ListBox コントロールがダブルクリックされたということを
親ウィンドウ(コントロールのオーナー)に対して通知するメッセージになります。

「マウスのダブルクリックをリストボックスに送る」のではなく、
『「リストボックスがマウスのダブルクリックを受け取った」という通知を「親ウィンドウ」に送る』

という処理をします。
操作をエミュレートするのではなく、そういう操作が発生したという結果だけを送ってやるという形です。

ちなみに、これができるのは、先にも書いていますが、Windows.Formsアプリまたは Win32 Native なアプリだけです。

アプリがどういうものかわからない場合は、Spy++ などで、ウィンドウをたどってみるのが一番わかりやすいです。

一応。。。送って処理されることが期待できるアプリであれば、

SendMessage( 親ウィンドウのハンドル, LVN_DBLCLK, MAKEWPARAM( xPos, yPos ), 0 );

という形で送信できます。

MAKEWPARAM は OS提供のマクロですが、C/C++ 向けなので C#の場合は、
IntPtr MAKEWPARAM( int low, int hight ){ return (low | (high)<<16); }
という形で定義します。

SendMessage は世に多数転がっていますが、DLLからエクスポートされてる形をストレートに書くと下記のようになります。

[DllImport("user32.dll", CharSet=CharSet.Unicode, SetLastError=true, CallingConvention=CallingConvention.Winapi)]
static LONG SendMessageW( IntPtr hwnd, Uint32 msg, IntPtr wParam, IntPtr lParam );

[DllImport("user32.dll", CharSet=CharSet.Ansi, SetLastError=true, CallingConvention=CallingConvention.Winapi)]
static LONG SendMessageA( IntPtr hwnd, Uint32 msg, IntPtr wParam, IntPtr lParam );

どっちを使っても送信できますが、プロセスをまたぐので、W のほうがよいと思います。

ウィンドウハンドルの取得については、どうやってリストボックスを判定しているかがわからないので省略。

引用返信 編集キー/
■94632 / inTopicNo.5)  Re[4]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ とっちゃん (675回)-(2020/04/26(Sun) 14:49:31)
No94631 (とっちゃん さん) に返信

一つ間違い。

SendMessage の戻り値は、LRESULT なので、.NET だと IntPtr です。うっかりしてた。
引用返信 編集キー/
■94645 / inTopicNo.6)  Re[5]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Tom (16回)-(2020/04/27(Mon) 18:25:22)
とっちゃん さま

>『「リストボックスがマウスのダブルクリックを受け取った」という通知を「親ウィンドウ」に送る』
この場合の親ウィンドウとはリストボックスが乗っているコンテナのハンドル でしょうか? アプリのハンドルではなく。

>SendMessage( 親ウィンドウのハンドル, LVN_DBLCLK, MAKEWPARAM( xPos, yPos ), 0 );
あー、やっぱりマウスクリックする座標指定が必要ですよね・・・
それを得る方法が皆目見当がつかないのです・・・

>ウィンドウハンドルの取得については、どうやってリストボックスを判定しているかがわからないので省略。
実は対象のリストボックス、タブシートの上に配置されており、タブシート(幸いシートは1つという条件)を
アクティブにすると自動的にリストボックスにフォーカスが乗る という仕様のアプリだったため、何とかなっていました。
そのため、厳密にはリストボックスのハンドル、取得出来てません。
しかしキー送信すればちゃんと動いてくれたので問題ない状態でした。

先ほどの親ウィンドウってこのタブシートのことになるのか、SPY++を使ってみないとわからないですが、
そもそも座標を得る方法がないともうお手上げな感じが致します。

なにかいい方法、ご存じないでしょうか?
引用返信 編集キー/
■94646 / inTopicNo.7)  Re[6]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ とっちゃん (679回)-(2020/04/27(Mon) 20:55:29)
No94645 (Tom さん) に返信
> >SendMessage( 親ウィンドウのハンドル, LVN_DBLCLK, MAKEWPARAM( xPos, yPos ), 0 );
> あー、やっぱりマウスクリックする座標指定が必要ですよね・・・
> それを得る方法が皆目見当がつかないのです・・・
>
すいません。メッセージの送信方法が違ってました。

SendMessage( 親ウィンドウ, WM_COMMAND, MAKEWPARAM( index, LVN_DBLCLK ), リストボックスのウィンドウハンドル );

でした。全然違う。。。orz

なので、マウス座標は必要ありませんでした。すいません。



> >ウィンドウハンドルの取得については、どうやってリストボックスを判定しているかがわからないので省略。
> 実は対象のリストボックス、タブシートの上に配置されており、タブシート(幸いシートは1つという条件)を
> アクティブにすると自動的にリストボックスにフォーカスが乗る という仕様のアプリだったため、何とかなっていました。
> そのため、厳密にはリストボックスのハンドル、取得出来てません。
> しかしキー送信すればちゃんと動いてくれたので問題ない状態でした。
>
> 先ほどの親ウィンドウってこのタブシートのことになるのか、SPY++を使ってみないとわからないですが、
> そもそも座標を得る方法がないともうお手上げな感じが致します。
>
> なにかいい方法、ご存じないでしょうか?

キー送信というのは、SendKeys.Send() を使った場合とかですかね?

どちらにしても、Spy++ でウィンドウとかを調べないとだめですね。

具体的なアプリがわからないので、そもそも LVN_DBLCLK でいいかどうかもわからないですが。。。

引用返信 編集キー/
■94647 / inTopicNo.8)  Re[6]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Hongliang (1018回)-(2020/04/27(Mon) 20:55:40)
> >『「リストボックスがマウスのダブルクリックを受け取った」という通知を「親ウィンドウ」に送る』
> この場合の親ウィンドウとはリストボックスが乗っているコンテナのハンドル でしょうか? アプリのハンドルではなく。
そうなりますね。
ただ、LBN_DBLCLKは直接SendMessageの第2引数にするのではなくて、第2引数はWM_COMMANDになりますね。
https://docs.microsoft.com/en-us/windows/win32/controls/lbn-dblclk
LBN_DBLCLKは第3引数wParamの上位16ビット部分。下位16ビットにコントロールIDというそのアプリのそのコントロール独自のID。
第4引数がリストボックスのウィンドウハンドル。

> >SendMessage( 親ウィンドウのハンドル, LVN_DBLCLK, MAKEWPARAM( xPos, yPos ), 0 );
> あー、やっぱりマウスクリックする座標指定が必要ですよね・・・
上記の通り、LBN_DBLCLKでは座標は含まれませんね。
アイテムの座標を取得するとなるとLBM_GETITEMRECTを使うことになりますが、こいつはポインタを要求するのでReadProcessMemoryを中心とするいろいろ面倒な手順が必要になります。

なんにせよ、とにかくSpy++で対象のアプリを観察しないと話は進まないでしょう。
引用返信 編集キー/
■94653 / inTopicNo.9)  Re[7]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Tom (17回)-(2020/04/28(Tue) 22:03:04)
とっちゃん さま
Hongliang さま

アドバイスありがとうございます。

>SendMessage( 親ウィンドウ, WM_COMMAND, MAKEWPARAM( index, LVN_DBLCLK ), リストボックスのウィンドウハンドル );
をいろいろ手を変え品を変えと試したのですが動作していない模様です。

親ウィンドウはタブシートだったので、SysTabControl32の部品のハンドルをセット
indexは3(3行目の意味)をセット
(LVN_DBLCLK はLBN_DBLCLKの誤字でしょうか?)
リストボックスはSysListView32の部品のハンドルをセット
この設定で正しいでしょうか?
(2つのハンドルはそれぞれ全部品の中から1つしかなかったので、誤って別の部品のハンドルってことはあり得ません)

あと、マウス座標指定は必要ないようなので忘れることにします。

なんかかなりいいところまで来ているような気がするのですが、どうにも煮詰まっています。
お知恵をお借りできませんでしょうか?

引用返信 編集キー/
■94655 / inTopicNo.10)  Re[8]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ とっちゃん (680回)-(2020/04/28(Tue) 22:56:58)
No94653 (Tom さん) に返信
> (LVN_DBLCLK はLBN_DBLCLKの誤字でしょうか?)
誤字です。

が!!!!
> リストボックスはSysListView32の部品のハンドルをセット
> この設定で正しいでしょうか?

SysListView32は、「リストボックス」ではなく「リストビュー」と呼ばれるものです。
リスト上に表示されるというところが似てるだけで全く別のコントロールです。

> なんかかなりいいところまで来ているような気がするのですが、どうにも煮詰まっています。
> お知恵をお借りできませんでしょうか?
> 
リストビューでのダブルクリックとなるとまた全然変わってきます。

こっちは、座標がいるんじゃなかったかなぁ?

リストボックスは、クラス名が "ListBox" で、C# で言えば、System.Windows.Forms.ListBox に当たるコントロールです。
それに対し、リストビュー(SysListView32)は、System.Windows.Forms.ListView にあたるコントロールです。
ちなみに SysTabControl32 は、C# で言えば、System.Windows.Forms.TabControl に当たるコントロールです。


で、ダブルクリックですが、WM_NOTIFY という通知メッセージをSendMessage する必要があります。
が、こちらは構造体をLPARAMに指定するうえ、座標も必要になりますので、かなり面倒なことになります。

Hongliang さんがちらっと書いていますが、ReadProcessMemory など相手プロセス上のメモリを操作するAPIを
駆使できないと、実現できません。APIがわかれば、ちょいちょいと組めるようなものでもありません。

比較的お手軽なのは、SendInput API を使って、実際のマウスの動きをエミュレーションする方法ですが
こちらは画面上の座標が必要なのと、当たり前ですが、操作したいウィンドウが最前面にないと操作できないので

> マウスカーソルがそこまで届かない場所(デスクトップ描画外)にいる場合はありえます。
と最初に書いていたように、画面外にいるとどうなるかはわかりません(試したことないので...)



と、今度は別の部分。

> 親ウィンドウはタブシートだったので、SysTabControl32の部品のハンドルをセット
> indexは3(3行目の意味)をセット

> リストボックスはSysListView32の部品のハンドルをセット
> この設定で正しいでしょうか?
> (2つのハンドルはそれぞれ全部品の中から1つしかなかったので、誤って別の部品のハンドルってことはあり得ません)

ん?このウィンドウ関係は違う気がします。

C#も含め、タブとリストの2つなら以下のような画面構成になっているはずです。
(Spy++的な表現)

+ 親ウィンドウ(コントロールの張り付いているフォーム)
  + タブコントロール(SysTabControl32)
 + リストビューコントロール(SysListView32)

タブとリストで親子関係だとこんな感じになってると思います。

+ タブコントロール(SysTabControl32)
  + リストビューコントロール(SysListView32)

引用返信 編集キー/
■94659 / inTopicNo.11)  Re[9]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Tom (18回)-(2020/04/29(Wed) 17:32:58)
とっちゃん さま

>SysListView32は、「リストボックス」ではなく「リストビュー」と呼ばれるものです。
うあ。
わたくし、そもそも出発点から間違っていたのですね…

>こっちは、座標がいるんじゃなかったかなぁ?
やっぱり座標が… って絶望してたら、ちょっとだけ思いました。
「あれ?座標に移動してダブルクリックするだけなら、そもそもハンドルとかも要らないんじゃ?」
で、いろんなサイトを参考にしてこんなコードをまずは書きました。
int Old_xPos;
int Old_yPos;
Old_xPos = System.Windows.Forms.Cursor.Position.X;
Old_yPos = System.Windows.Forms.Cursor.Position.Y;
SetCursorPos(New_xPos, New_yPos);//New_xPosとNew_yPosは事前に取得
//この4つをセットでダブルクリック
mouse_event(WM_LBUTTONDOWN, 0, 0, 0, 0);
mouse_event(WM_LBUTTONUP, 0, 0, 0, 0);
mouse_event(WM_LBUTTONDOWN, 0, 0, 0, 0);
mouse_event(WM_LBUTTONUP, 0, 0, 0, 0);
SetCursorPos(Old_xPos, Old_yPos);//元の位置に戻す

とりあえず期待通りの動き(マウスカーソルの残骸が残っていましたが)をしましたので、
あとはどうやって対象行の座標を得るか ってことに収束しました。
しかし、それこそが Hongliang さまのおっしゃる通り難易度がかなり高そうです。

で、いろいろ調べていたら
https://oshiete.goo.ne.jp/qa/4558229.html
でリストビューの情報の取り方がありました。
そこで
public const uint LVM_GETITEMCOUNT = (0x1000 + 4);
int nItems = SendMessage(hWnd, LVM_GETITEMCOUNT, 0, 0);
を行ってみたところ、リストには値があるのにもかかわらず戻り値は0でした。
どうも使い方を誤っているようです。

ここが間違っている等のアドバイスを頂けませんでしょうか?

余談
こんなことも試していました。
リストビューのフォーカス行はあらかじめ先頭行に移動してから該当行に移動します。
(同じ値の行のなかで先頭行にフォーカスしたいため)
で、今回制御しようとするアプリでHOMEキーを送信してから該当行に移動するボタンクリックします。
つまり、該当行に移動する前と移動後のタイミングが取れるのです。
そこで移動前のスクリーンショットと移動後のスクリーンショットを取得し、比較することで
座標を得ようとしました。
なんとうまくいきました!
が、別アプリのある個所に該当行の値が表示されており、そこを拾ってしまうのでボツとなりました…
(その個所をデスクトップ画面から外に出すと動作しました。
 また別問題としてデスクトップ画面の「拡大縮小とレイアウト」が100%でないとズレることもわかりました)

道は険しいです。
引用返信 編集キー/
■94663 / inTopicNo.12)  Re[10]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ とっちゃん (681回)-(2020/04/29(Wed) 20:54:05)
No94659 (Tom さん) に返信

> >こっちは、座標がいるんじゃなかったかなぁ?
> やっぱり座標が… って絶望してたら、ちょっとだけ思いました。
> 「あれ?座標に移動してダブルクリックするだけなら、そもそもハンドルとかも要らないんじゃ?」

確かにマウス位置を動かすだけであればウィンドウハンドルは必要ないです。


> しかし、それこそが Hongliang さまのおっしゃる通り難易度がかなり高そうです。
>
> で、いろいろ調べていたら
> https://oshiete.goo.ne.jp/qa/4558229.html
> でリストビューの情報の取り方がありました。
> そこで
> public const uint LVM_GETITEMCOUNT = (0x1000 + 4);
> int nItems = SendMessage(hWnd, LVM_GETITEMCOUNT, 0, 0);
> を行ってみたところ、リストには値があるのにもかかわらず戻り値は0でした。
> どうも使い方を誤っているようです。
>
hWnd は、リストビューのウィンドウハンドルになっていますか?
それ以外はぱっと見ではわからないな。



> 余談
> こんなことも試していました。
> リストビューのフォーカス行はあらかじめ先頭行に移動してから該当行に移動します。
> (同じ値の行のなかで先頭行にフォーカスしたいため)
> で、今回制御しようとするアプリでHOMEキーを送信してから該当行に移動するボタンクリックします。
> つまり、該当行に移動する前と移動後のタイミングが取れるのです。

ん?タイミングの取得というのは?
というか。。。制御したいアプリのプロセスとの関係は?
いまいちどころか状況が良くつかめないのですが?


> そこで移動前のスクリーンショットと移動後のスクリーンショットを取得し、比較することで
> 座標を得ようとしました。
> なんとうまくいきました!
> が、別アプリのある個所に該当行の値が表示されており、そこを拾ってしまうのでボツとなりました…
> (その個所をデスクトップ画面から外に出すと動作しました。
>  また別問題としてデスクトップ画面の「拡大縮小とレイアウト」が100%でないとズレることもわかりました)
>
HighDPI 問題かな?
すいません。読解力が低くて、状況がよくわからないので理解できない。。。


> が、別アプリのある個所に該当行の値が表示されており、そこを拾ってしまうのでボツとなりました…

操作したいアプリの上にウィンドウがある場合でも制御したいということでしょうか?
それだと mouse_event などの通知先は画面を隠している別のアプリのウィンドウになってしまうので使えませんよ。

引用返信 編集キー/
■94676 / inTopicNo.13)  Re[11]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Tom (19回)-(2020/04/30(Thu) 17:26:03)
とっちゃん さま

>hWnd は、リストビューのウィンドウハンドルになっていますか?
申し訳ありません、こちらのミスでした。
ただしいハンドルにしたところちゃんと取れました。

が、結局それで取れる値では本当にこちらの期待する座標を得られるか怪しいのです。
欲しい座標を求めるには
 別アプリの左上座標+別アプリ内でのリストビュー位置+(選択行番号ーTOP行番号)*一行の高さ
を計算したうえで、更にHighDPI問題?ってのを解決しないとならないのは、
わたくしには難易度高すぎと判断しました。
それにそもそも選択行がデスクトップ画面内に収まっているという保証がないのですし、
別アプリはセカンダリモニタに配置されているかもしれませんし。

以上を踏まえ、残念ながらあきらめることといたします。
これまで再三にわたりアドバイスをしてくださり、本当にありがとうございました。
また、お知恵をお借りしたいときにはこちらに書き込むと思いますが、
お暇でしたらその時にまたアドバイスを頂けると幸いです。

解決済み
引用返信 編集キー/
■94724 / inTopicNo.14)  Re[12]: 別アプリのリストボックス上のフォーカス行をWクリックしたい
□投稿者/ Tom (21回)-(2020/05/10(Sun) 11:49:31)
後日談

上の書き込みをしたあと数日たってハタと気が付きました。
「リストビューもウィンドウの一種なんだからハンドルさえわかればそのままスクリーン座標取れるよね?
 で、座標内のスクリーンショットから変化した座標って3つ上の書き込みでしたとおり取得できてたよね?
 デスクトップ画面からはみ出たのはそもそもマウス行かないんだから、仕様としていいんじゃない?」
そう思って作ったらあっけなくできちゃいました。
ただ、スクリーンショットを取得する範囲が大きいと完全に取得する前に次の処理を行ってしまい取りこぼしが
発生していたので、タイミングをとるのにちょっとだけ難儀しました。

あとHighDPI問題はマニフェストファイルってのを「参照の追加」で新規追加したらもうコードが書かれている
ファイルが作成されるので、ある個所のコメントを解除するだけ という記事をみてその通りにしたら
こちらもあっけなく解決しました。
セカンダリモニタでも座標は何の問題もなく取れました。

あれだけ悩んでいたのがウソのように解決でき、拍子抜けだったです。
引用返信 編集キー/

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


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

このトピックに書きこむ