ドロップダウンリストの自作

解決


みりん  2008-10-29 02:06:33  No: 69166

ドロップダウンリストを自作していて疑問に思ったのですが、

Windows 標準のドロップダウンリストは、
ドロップボタンを押して表示したリストボックスを
どのようなイベントをトリガーにして閉じているのでしょうか?

リストボックスの領域内がクリックされた場合は、
イベントが拾えますので、
問題なくリストボックスを閉じることができるのですが、

リストボックスの領域外がクリックされた場合の、
トリガーとなるイベント ( の拾い方 ) がわかりません。

最初は、
リストボックスにフォーカスを設定しておいて、
WM_KILLFOCUS が発生したら、
リストボックスを閉じるようにしていました。

これはうまくいきました。

しかし、いろいろと調べていると、
Windows 標準のドロップダウンリストのリストボックスの親はデスクトップで、
そのスタイルに WS_CHILD が指定されているというがわかりました。

そこで、
親をデスクトップに、そのスタイルに WS_CHILD を指定して
リストボックスを作成してみましたところ、
WM_KILLFOCUS が発生しなくなってしまい、
リストボックスを閉じることができなくなってしまいました。

メッセージフック用 DLL を作成するという手もあると思いますが、
それはちょっと大掛かりすぎるように思います。

何か良い方法をご存知の方がいらっしゃいましたら、
ご教示いただけませんでしょうか?

環境:
WindowsXP SP2


みりん  2008-10-29 02:08:59  No: 69167

すみません。
文章が途中で切れてしまいました。

環境:
WindowsXP SP2
Visual C++ 2005


subaru  2008-10-29 02:37:27  No: 69168

マウスをキャプチャーしておけば
領域外のクリックも拾えると思います。


みりん  2008-10-29 02:47:36  No: 69169

ご返信ありがとうございました。

仰るとおり、
単純なドロップダウンリストであれば、
マウスのキャプチャーだけで対処できると思います。

ただ、
質問には書き忘れていましたが、
そのリストボックス上に
スクロールバーやその他のコントロールをのせたいと思っています。
その場合、
単純にマウスのキャプチャーだけでは処理できないように思います。

実際、
マウスのキャプチャーを用いた方法でもいろいろ試しているのですが、
なかなかうまくいきません。
( やり方が悪いだけかもしれませんが・・・)


夏みかん  2008-10-29 04:49:55  No: 69170

全体のイメージがつかめませんが
次のメッセージが参考になりませんかね。
どうでしょうか?

WM_CAPTURECHANGED
http://www.winapi-database.com/Message/WM/WM_CAPTURECHAINGED.html

WM_CANCELMODE
http://www.winapi-database.com/Message/WM/WM_CANCELMODE.html


abc  2008-10-29 18:26:11  No: 69171

つまり、
マウスイベントがどうとかということではなく、
フォーカスを失ったこと ( または、それの代わりになるなにか ) を
知る方法がないかってこと?

簡単に言えば、
Windows のドロップダウンリストってどういう実装なの? ってこと?


仲澤@失業者  2008-10-29 19:24:17  No: 69172

メニューやコンボボックスの「リストを閉じる」
タイミングは「リスト自身がアクティブでなくなる」
時点です。つまりWM_ACTIVATEですね。

これは、表示している「リストボックス自身」が応答すべき
メッセージなので、SPY++等では捕捉することができない
例です(消えちゃうんだもん)。

また、WM_KILLFOCUSは来ない場合もありえますので
頼りになりません。注意してください。


gak  2008-10-30 01:57:03  No: 69173

> どのようなイベントをトリガーにして閉じているのでしょうか?
リストボックス表示時に SetCapture() して、WM_LBUTTONDOWN 時に ReleaseCapture() して
(マウス位置がスクロールバー等の上でなければ)閉じる。
スクロールバー等の上で WM_LBUTTONDOWN された時は、ReleaseCapture() 後に
WM_NCLBUTTONDOWN を自力で SendMessage() してスクロール処理等を誘発、その後再 SetCapture()。

って感じなんだろうか。

> ドロップボタンを押して表示したリストボックス
みりんさんは既に知っている感じもするけど一応書いとく。
通常のリストボックスのウィンドウクラス名は”ListBox”だけど、上記リストボックスの
クラス名は”ComboLBox”。
つまり、通常のリストボックスとは似て非なるコントロールだという事を頭の片隅にでも
置いておくと良いかも。(大した違いは無い気もするけどね)

> SPY++
知ってるかも知れないが情報 2。
GetComboBoxInfo() 等でリストボックスのウィンドウハンドルを得て、Spy++でウィンドウ検索
とすれば該当ウィンドウを見つけられる。それでメッセージを追いかける事が可能。


abc  2008-10-30 02:44:32  No: 69174

> WM_ACTIVATE
WM_ACTIVATE はトップレベルウインドウにしか届かなないはずなので、
子ウインドウでは WM_ACTIVATE をハンドリングできないのでは?


subaru  2008-10-30 03:00:33  No: 69175

ドロップダウンした部分がフォーカスを持つなら
ドロップダウンリストよりもDateTimePickerコントロールの
親子関係を参考にした方がいいかもしれない。


みりん  2008-10-30 19:44:59  No: 69176

subaruさん、夏みかんさん、abcさん、仲澤@失業者さん、gakさん
ご返信ありがとうございます。

アドバイスを参考に
WM_FOCUS, WM_ACTIVATE やマウスイベントでの実装をいろいろ試しているのですが、
なかなかうまくいきません。

DateTimePickerコントロールについては、
これから調べてみようと思います。


みりん  2008-10-30 20:58:56  No: 69177

DateTimePicker 調べてみました。
DateTimePicker のウインドウスタイルには WS_POPUP が指定されていました。

ウインドウスタイルに WS_POPUP を指定した自作ドロップダウンリストの作成には
既に成功しています。
ただ、ドロップダウンリストにフォーカスが移ると、
アプリケーションが非アクティブになってしまうのが難点ですが。
( アプリケーションが非アクティブになってしまうのは DateTimePicker も同じ )

ComboLBox についてももう少し調べてみたところ、
マウスキャプチャーしているようでもあり、していないようでもありと、
よくわかりません。

通常、マウスキャプチャーしていれば、マウスが画面上のどこに移動しても、
キャプチャーしたウインドウに対して、マウス関係のメッセージが飛んでくるはずですが、
ComboLBox の場合は、ある領域 ( アプリケーションのウインドウ内 ) から出ると、
とたんに、マウス関係のメッセージが飛んでこなくなります。

うーん、どうなってるんでしょう???


gak  2008-10-31 02:13:13  No: 69178

> 通常、マウスキャプチャーしていれば、マウスが画面上のどこに移動しても、
> キャプチャーしたウインドウに対して、マウス関係のメッセージが飛んでくるはずですが、
MSDN英語版によると
> SetCapture captures mouse input either when the mouse is over the capturing window,
> or when the mouse button was pressed while the mouse was over the capturing window
> and the button is still down.
「SetCapture()は、キャプチャしている(SetCapture()で指定した)ウィンドウ上にマウスがある時、
又は、キャプチャしているウィンドウ上でマウスが押され、まだ離されていない時にマウス入力を
キャプチャします。」
という訳で多分良いと思う。違ったらゴメン。

つまり、マウスが押されていない時は他ウィンドウに対するマウス入力はキャプチャしないって事らしい。

> ComboLBox の場合は、ある領域 ( アプリケーションのウインドウ内 ) から出ると、
> とたんに、マウス関係のメッセージが飛んでこなくなります。
なので、みりんさんが書いた上記の動作も正常なんじゃないかな。

ちなみに、みりんさんが「どこに移動してもメッセージが飛んでくる」と勘違いした(というか騙された)
のは腐れMSDN日本語版のせいだと思われる。
一度見比べてみると良いかも。変な笑いと妙な破壊衝動が沸いてくるかもしれないけどね


仲澤@失業者  2008-10-31 03:29:59  No: 69179

abc さんへ。
>WM_ACTIVATE はトップレベルウインドウにしか届かなないはずなので、
>子ウインドウでは WM_ACTIVATE をハンドリングできないのでは?

コンボボックスのリストボックス(ドロップダウン)
は「トップレベルウインドウ(親がデスクトップ)」です。
すぐ消えるウインドウは、トップでいいわけです。
邪魔にならんし。


abc  2008-10-31 03:57:40  No: 69180

仲澤@失業者 さんへ

確かに、コンボボックスのリストボックス の親はデスクトップなのですが、
ウインドウスタイルに WS_CHILD ( WS_CHILDWINDOW ) が指定されているので、
トップレベルウインドウではないと考えたのですが・・・。


仲澤@失業者  2008-11-04 19:31:01  No: 69181

すみません。言葉が足りなかったようです。

スレ主さんのやりたいことを実現するのに重要なのは
「トップレベル」であることです。
つまり、ウインドウスタイルはWS_POPUP | WS_VISIBLE 
拡張が  WS_EX_TOPMOST  なのであって、
WS_CHILDを付けちゃだめです。
という意味だったのです。


みりん  2008-11-04 23:07:58  No: 69182

gak さん、仲澤@失業者 さん、abc さん、
ご返信ありがとうございます。

> > SetCapture captures mouse input either when the mouse is over the capturing window,
> > or when the mouse button was pressed while the mouse was over the capturing window
> > and the button is still down.
> つまり、マウスが押されていない時は他ウィンドウに対するマウス入力はキャプチャしないって事らしい。
すみません、説明が抜けていました。

通常、マウスキャプチャーしていれば、マウスが画面上のどこに移動しても、
キャプチャーしたウインドウに対して、マウス関係のメッセージが飛んでくるはずですが、
ComboLBox の場合は、ある領域 ( アプリケーションのウインドウ内 ) から出ると、
とたんに、マウス関係のメッセージが飛んでこなくなります。
  ↓
(補足)
  ↓
通常、マウスキャプチャーしていて かつ マウスボタンが押しっぱなし ならば、
マウスが画面上のどこに移動しても、
キャプチャーしたウインドウに対して、マウス関係のメッセージが飛んでくるはずですが、
マウスボタンを押すのと同時にキャプチャーを行い、
その後もずっとマウスボタンを押し続けているのにも関わらず、
ComboLBox の場合は、ある領域 ( アプリケーションのウインドウ内 ) から出ると、
とたんに、マウス関係のメッセージが飛んでこなくなります。

結局、
いろいろと試してみたのですが、
WS_CHILD ではどうしてもうまくいきませんでした。
ですので、
今回は WS_POPUP で作成することにしました。
( 細かいところで不満はあるのですが )

ご助言下さった皆さん、ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加