トップ回答者
ダイアログに配置したコントロール上でのマウスイベントを、ダイアログ上のイベントとして取得したい

質問
-
いつもお世話になっております。arubi-momoと申します。
現在、Windows7 64bit、VisualStudio2010 VC++(MFC)で開発中です。
ダイアログ画面の、あるコントロール上からドラッグを行い、SDIへドロップするプログラムを作成しています。
ドラッグ開始時に必要な情報を変数に保存し、ダイアログ画面上でLButtonUpがされた場合、保存した情報を初期化するということをおこなっておりますが、LButtonUpがされる場所が、ダイアログ画面に配置されたコントロール(ボタンなど)の上だった場合、ダイアログ画面のLButtonUpイベントが発生せず、初期化が行われないという現象が起きています。
コントロールがDisable状態の場合は、コントロールを無視してダイアログ上のイベントに入っていくのですが、コントロールがEnableの場合でも、画面側のLButtonUpのイベントを有効にする方法はないでしょうか。
ご存知の方がいらっしゃいましたら、ご教示いただければ幸いです。
何卒、よろしくお願いいたします。
回答
-
コントロール上で LButtonUp したときに、コントロールが有効の場合、コントロールに対して LButtonUp イベントが発生し、コントロールの親にはイベントが伝わらないということだと思います。
一つの案ですが、SetCapture() を使用すると、カーソルの位置に関係なく、以降のすべてのマウス入力が SetCapture() を呼び出したウィンドウに送られるようになります。ドラッグ開始時にドラッグ元のウィンドウで SetCapture() を呼び出し、呼び出したウィンドウで LButtonUp イベントを受信し、マウスカーソルの位置がドラッグ対象のウィンドウ(この場合は SDI ?)にある場合は、必要な処理を行うようにしてはいかがでしょうか? ドラッグ後にすべてのマウスイベントを受信しないようにするため LButtonUp のタイミングで ReleaseCapture() を呼び出す必要があります。
参考サイト: https://msdn.microsoft.com/ja-jp/library/1a2b3a4c.aspx
- 編集済み kenjinoteMVP 2017年6月22日 6:41
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年6月22日 6:53
- 回答としてマーク arubi_momo 2017年6月27日 6:08
-
ダイアログ(内のコントロール)から別のウィンドウ(SDI)にドロップするということですか?
そこでマウスの形状も処理したいと。。。
であれば、今までと実装は変わりますが、COleDataSource を使ったDrag&Dropの方が標準でマウスカーソルも処理してくれます(自分でいろいろ細工することも可能)し、ドラッグドロップの基本がドロップされる側が自分は受け入れ可能か?
という形でチェックするので、ドロップする側と、される側の処理がわかりやすくなります。
COleDataSource を使えば
>何も処理せずマウスカーソルを戻したいのですが、ダイアログ画面のコントロール上で左ボタンを離してしまった場合
というあたりも特に考慮不要です。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
- 回答としてマーク arubi_momo 2017年6月27日 6:08
-
全て自前で実装するのは非常に困難だと考えられます。
やはり COleDataSource、COleDropTarget、COleDataObject クラスを使用する方法をお勧めします。名前からも予測できる通り、これらのクラスの実装にはOLEという仕組みが使われています。
これらを使用する場合、全てのOLE処理に先立って、AfxOleInit()
を実行する必要がある点に注意が必要です。
ごくおおざっぱに仕組みを説明すると
1.ドラッグサーバー側は転送用データを作成し COleDataSource を使ってドラッグを開始します。
2.クライアント側は COleDropTarget を構築してそのRegister()をコールして、
自身(this=CWnd*)を登録しておきます。これによってドロップ関連のメッセージが来るようになります。3.this(=CWnd*)は必要に応じて OnDragEnter()、OnDragLeave()、OnDragOver()等に応答します。
4.特に、ドロップされた場合にはCOleDropTargetは、登録されているCWndに対して
OnDrop( COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
を呼び出します。pDataObjectにサーバー側で用意したデータが入っています。以上に出現するキーワードで検索すれば、実装例が見つかると思います。
- 回答としてマーク arubi_momo 2017年6月27日 6:08
すべての返信
-
コントロール上で LButtonUp したときに、コントロールが有効の場合、コントロールに対して LButtonUp イベントが発生し、コントロールの親にはイベントが伝わらないということだと思います。
一つの案ですが、SetCapture() を使用すると、カーソルの位置に関係なく、以降のすべてのマウス入力が SetCapture() を呼び出したウィンドウに送られるようになります。ドラッグ開始時にドラッグ元のウィンドウで SetCapture() を呼び出し、呼び出したウィンドウで LButtonUp イベントを受信し、マウスカーソルの位置がドラッグ対象のウィンドウ(この場合は SDI ?)にある場合は、必要な処理を行うようにしてはいかがでしょうか? ドラッグ後にすべてのマウスイベントを受信しないようにするため LButtonUp のタイミングで ReleaseCapture() を呼び出す必要があります。
参考サイト: https://msdn.microsoft.com/ja-jp/library/1a2b3a4c.aspx
- 編集済み kenjinoteMVP 2017年6月22日 6:41
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年6月22日 6:53
- 回答としてマーク arubi_momo 2017年6月27日 6:08
-
SetCapture案は出ているので、もう一つの案として、COM アーキテクチャの DoDragDrop API(ラッパー)を提示しておきます。
MFCアプリなので、ドラッグを開始する側は、COleDataSource を使えばよいと思います。送信したいデータを CacheGlobalDataで載せてやれば、あとは DoDragDrop メソッドを呼び出すだけです。
受け取る側が、CView の派生クラスなら(CScrollView や CFormViewでも同じ)、メンバー変数に COleDropTarget を用意して、OnCreate で、Registerメソッドを呼び出すようにして、あとは、OnDragEnter, ONDragLeave, OnDragOver, OnDrop とオプションで OnScrollBy を実装します。
CView以外のウィンドウ(任意のコントロールを含む)の場合は、COleDropTarget の派生クラスを用意してCView同様仮想関数を実装して受け取り出来るようにします。
実装詳細等々は、MSDNライブラリにあるのでそちらを参考にしてください。
初期実装コストはちょっと高めになりますが。。。ドラッグドロップを分離できるので融通の利く実装になります。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
- 回答の候補に設定 立花楓Microsoft employee, Moderator 2017年6月22日 6:53
-
kenjinote様、とっちゃん様
ご返答ありがとうございます!
早速SetCaptureを試させていただきました。最初の投稿には詳しく記載しなかったのですが、ドラッグ操作の種類が2操作ありまして、片方はうまくいったのですが、片方はまだ解決できませんでした。
ダイアログ上のLButtonDown→MouseMove→LButtonUpの流れでドラッグを行う処理については、ご教示いただいたSetCaptureで対応できました。
もう1つのほうは、ダイアログ上のListControl拡張クラスのOnLvnBeginDragのイベントを利用して、ドラッグを開始しています。ListControlからドラッグを開始して、ダイアログ画面上でLButtonUpをした時に、初期化処理をしたいのですが、ListControl上からのドラッグ開始は、親画面のイベントとしては認識されないため、SetCaptureが使用できませんでした。
ListControlの拡張クラスで、ListControlのLButtonDownを作成して、GetParent()->SetCapture()として、親画面のSetCaptureを行ってみたのですが、うまくいきませんでした。
とっちゃんさまにご教示いただいた方法も試そうかと思ったのですが、現在できているドラッグ&ドロップのつくりを大幅に変更することになるため、SetCaptureの応用で対応できるのであればそちらの方法を選択したいと考えております。やはり根本から作り替えないと実現は難しいでしょうか。
お知恵を拝借できれば幸いです。
勉強不足で申し訳ありませんが、よろしくお願いいたします。
-
OnLvnBeginDrag トリガーに変わっているというだけで、段取りそのものは同じです。
が、何がどうダメなのかわからないので、何をどうすればいいのかは答えられません。
ダイアログ画面と、親画面は同じものですか?それとも異なるものですか?
ちなみに、リストコントロールのOnLvnBegnDragがくる場合、ダイアログ(リストコントロールの親ウィンドウ)のLButtonDown は呼ばれません。
なので、ダイアログのLbuttonDownでいろいろやってる場合、そこで行っている処理をメソッドアウトして、共通呼び出し出来るようにするなどの修正が必要になります。
これ以上の詳細は具体的なコードなどがわからないので何とも。。。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
-
すこし違う意見となります。
一般にマウスによるD&Dの機能としては、カーソル形状などの変更を利用するなどして、
ドロップ可能かどうかをユーザーに示す動作が標準的だと考えます。ドロップできないコントロール上等では「交通標識の車両通行止めの形状」等のカーソルでその意味を示し。
ドロップ可能な場所ではその意味を示すカーソルに等に変更するべきだと考えます。
これにより、ユーザーはドロップが受け入れ可能かどうかがすぐに理解でき、
無駄な試行を繰り返さなくて済みます。また、この仕様は標準的なものであって、一般的なユーザーにとっては常識であると考えられます。
つまり、本件においてはドロップできないコントロール上ではできなくてよいのが標準的実装であり、
むりやりドロップできるようにするべきではないかもしれません。MFCアプリケーションにおいては、以上の機能を最も簡便に実装する手段としては、
とっちゃんさんの発言にあるCOleDataObjectを使用した実装が一般的であると考えます。- 編集済み 仲澤@失業者 2017年6月22日 7:53
-
仲澤@失業者様
ご回答ありがとうございます。
説明がわかりにくくもうしわけありません。ドラッグ&ドロップ処理は、ダイアログ画面からSDIに対して行います。ドラッグ時にマウスカーソルを変更する処理を行っています。
ダイアログ画面上でドラッグを開始し、ダイアログ画面上で左ボタンを離してしまった場合は、何も処理せずマウスカーソルを戻したいのですが、ダイアログ画面のコントロール上で左ボタンを離してしまった場合、マウスカーソルが戻らないという事象に悩んでおります。
とっちゃん様にいただいた情報をもとに、もう少しチャレンジしてみようと思います。
また、仲澤@失業者様にいただいた、「ドロップできないコントロール上等では「交通標識の車両通行止めの形状」等のカーソルでその意味を示し」これは確かにその通りですので、やってみようと思います。ありがとうございます。
解決しなかった場合はまたご相談させていただくかもしれませんが、よろしくお願いいたします。
-
ダイアログ(内のコントロール)から別のウィンドウ(SDI)にドロップするということですか?
そこでマウスの形状も処理したいと。。。
であれば、今までと実装は変わりますが、COleDataSource を使ったDrag&Dropの方が標準でマウスカーソルも処理してくれます(自分でいろいろ細工することも可能)し、ドラッグドロップの基本がドロップされる側が自分は受け入れ可能か?
という形でチェックするので、ドロップする側と、される側の処理がわかりやすくなります。
COleDataSource を使えば
>何も処理せずマウスカーソルを戻したいのですが、ダイアログ画面のコントロール上で左ボタンを離してしまった場合
というあたりも特に考慮不要です。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
- 回答としてマーク arubi_momo 2017年6月27日 6:08
-
全て自前で実装するのは非常に困難だと考えられます。
やはり COleDataSource、COleDropTarget、COleDataObject クラスを使用する方法をお勧めします。名前からも予測できる通り、これらのクラスの実装にはOLEという仕組みが使われています。
これらを使用する場合、全てのOLE処理に先立って、AfxOleInit()
を実行する必要がある点に注意が必要です。
ごくおおざっぱに仕組みを説明すると
1.ドラッグサーバー側は転送用データを作成し COleDataSource を使ってドラッグを開始します。
2.クライアント側は COleDropTarget を構築してそのRegister()をコールして、
自身(this=CWnd*)を登録しておきます。これによってドロップ関連のメッセージが来るようになります。3.this(=CWnd*)は必要に応じて OnDragEnter()、OnDragLeave()、OnDragOver()等に応答します。
4.特に、ドロップされた場合にはCOleDropTargetは、登録されているCWndに対して
OnDrop( COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
を呼び出します。pDataObjectにサーバー側で用意したデータが入っています。以上に出現するキーワードで検索すれば、実装例が見つかると思います。
- 回答としてマーク arubi_momo 2017年6月27日 6:08
-
とっちゃん様、仲澤@失業者様
いつもお世話になっております。また、ご丁寧にご指南いただきましてありがとうございます。
>ダイアログ(内のコントロール)から別のウィンドウ(SDI)にドロップするということですか?
おっしゃる通りです。
昨日思い当たった問題についてもう一度確認したところ、解決はいたしました。うまくいかなかったのは、フラグの初期化のタイミング等が間違っていたことが原因で、単純なコーディングミスでした。一応、希望通りの実装ができたことはできたのですが。。。
とっちゃん様と仲澤@失業者様がおっしゃる通り、自分でかなり複雑なつくりにしてしまっているのだな、と感じました。自分で整理していても、どこでどんな処理をしているのかがとてもわかりにくかったです。だからミスしやすくなるんですね。今後のメンテナンスのことも考えて、大幅な変更は必要になるのですが、OLEを使用した形に変えてみようかと思います。
色々ご教示いただきましてありがとうございました。
今後ともどうぞよろしくお願いいたします。