スレッドとポップアップメニューについて
スレッドとポップアップメニューについて
一つのスレッドを持つプログラムで
マウスの右クリックからポップアップメニューを
表示させたいと思っているのですが、
そのメイン・スレッド内で「TrackPopupMenu」
を書いてもポップアップが表示されません。
イベント・コードの「RButtonDown」(Sub MainWnd_RButtonDown())
からTrackPopupMenuを実行するときちんと表示
されるのですが、どうしてスレッド内から呼び出せない
のか分からず悩んでいます。
もちろん上記のようにイベントから呼び出すことは
出来たのですが、それだとそのメニューの選択によって
スレッド内で使う変数に変化を加えようとすると
スレッドとの「同期」をさせなければならなくなるのかなとも
思ったりして、自分には大変だなと。なので出来れば
メインのスレッド内から呼び出して、メニューの選択が終わるまでは
スレッドも待機させておきたいと思うのですが
そのようなことは可能でしょうか?
よろしければ教えてください。
(AB ver4.04を使っています。)
※過去ログを調べていたら
No.1381 「TrackPopUpMenu」が同様の質問かな
とも思いました。ただ解決は分かりません。
マウスの右クリックからポップアップメニューを
表示させたいと思っているのですが、
そのメイン・スレッド内で「TrackPopupMenu」
を書いてもポップアップが表示されません。
イベント・コードの「RButtonDown」(Sub MainWnd_RButtonDown())
からTrackPopupMenuを実行するときちんと表示
されるのですが、どうしてスレッド内から呼び出せない
のか分からず悩んでいます。
もちろん上記のようにイベントから呼び出すことは
出来たのですが、それだとそのメニューの選択によって
スレッド内で使う変数に変化を加えようとすると
スレッドとの「同期」をさせなければならなくなるのかなとも
思ったりして、自分には大変だなと。なので出来れば
メインのスレッド内から呼び出して、メニューの選択が終わるまでは
スレッドも待機させておきたいと思うのですが
そのようなことは可能でしょうか?
よろしければ教えてください。
(AB ver4.04を使っています。)
※過去ログを調べていたら
No.1381 「TrackPopUpMenu」が同様の質問かな
とも思いました。ただ解決は分かりません。
スレッドとポップアップメニューについて
自己レスです。
とりあえず言っていたことは以下のようにして実行することができました。
まずメインのスレッド内でGetAsyncKeyState()で
右クリックの状態を取得。
右クリックが押されていたら、
「SendMessage(hMainWnd,WM_NULL,wp,lp)」
として、"WM_NULL"を送る。(このメッセージはもしかして
勝手に使ってもいいんじゃないかと思ったので…。確信はありません。)
MainWndProc()内でそのメッセージを受け取ったら、
「TrackPopUpMenu」でポップアップメニューを表示させる。
SendMessage()という命令は
「プロシージャが終了するまで制御が戻りません。
つまり、完全に同期がとれていると考えることができます。」
と説明にあったので、これで出来るのではないかと思って
やってみました。
あまりいいやり方ではないかも知れません。
もっと良い方法があればまた教えてください。
報告を兼ねて、自己レスでした。
※ただこの方法だとメインスレッドのループで
(GetAsyncKeyState()で)タイミングが悪いと
右クリックの判定を拾い損ねることがあって、
クリックしてもポップアップが表示されないことがあります。
あとこの方法と直接関係はないのですが、
ポップアップメニューが表示されている状態で、
マウスポインタを動かして、そこから更にもう一度右クリックすると
エクスプローラなどでは改めて新しい位置でポップアップが
立ち上がるのですが、自分の作ったプログラムでは、
TrackPopUpMenuだけではそうはならないみたいで
元の位置に表示されたままです。
これはどうすればいいのかな、と思っています。
とりあえず言っていたことは以下のようにして実行することができました。
まずメインのスレッド内でGetAsyncKeyState()で
右クリックの状態を取得。
右クリックが押されていたら、
「SendMessage(hMainWnd,WM_NULL,wp,lp)」
として、"WM_NULL"を送る。(このメッセージはもしかして
勝手に使ってもいいんじゃないかと思ったので…。確信はありません。)
MainWndProc()内でそのメッセージを受け取ったら、
「TrackPopUpMenu」でポップアップメニューを表示させる。
SendMessage()という命令は
「プロシージャが終了するまで制御が戻りません。
つまり、完全に同期がとれていると考えることができます。」
と説明にあったので、これで出来るのではないかと思って
やってみました。
あまりいいやり方ではないかも知れません。
もっと良い方法があればまた教えてください。
報告を兼ねて、自己レスでした。
※ただこの方法だとメインスレッドのループで
(GetAsyncKeyState()で)タイミングが悪いと
右クリックの判定を拾い損ねることがあって、
クリックしてもポップアップが表示されないことがあります。
あとこの方法と直接関係はないのですが、
ポップアップメニューが表示されている状態で、
マウスポインタを動かして、そこから更にもう一度右クリックすると
エクスプローラなどでは改めて新しい位置でポップアップが
立ち上がるのですが、自分の作ったプログラムでは、
TrackPopUpMenuだけではそうはならないみたいで
元の位置に表示されたままです。
これはどうすればいいのかな、と思っています。
Re: スレッドとポップアップメニューについて
TrackPopUpMenu関数の第2引数にTPM_RIGHTBUTTONを指定してみてください。> あとこの方法と直接関係はないのですが、
> ポップアップメニューが表示されている状態で、
> マウスポインタを動かして、そこから更にもう一度右クリックすると
> エクスプローラなどでは改めて新しい位置でポップアップが
> 立ち上がるのですが、自分の作ったプログラムでは、
> TrackPopUpMenuだけではそうはならないみたいで
> 元の位置に表示されたままです。
> これはどうすればいいのかな、と思っています。
右クリックでのメニュー選択・解除を受け付けるようになります。
レスありがとうございます。
イグトランスさん、レスありがとうございます。リストの整理に
手間取ってしまい返信を遅らせてしまいました。すみません。
メイン部分のソースを書きます。
プログラムは「マウスで左クリックした所を中心に、同心円を
外側へ向かって20個、さざ波が広がるようにゆっくり描いていく」
というものです。
右クリックでポップアップメニューを表示して、その選択により
描画色を変える。(ポップアップには「赤に変更」、「緑に変更」、
「青に変更」、の3つのメニュー項目)
ただし、もし20個の同心円の描画中に色が変えられた場合、
今描画している20個はそのままの色で描き、色の変更は次回から
反映させるものとする。
(プログラムはAB 4.04のプロジェクトで作っています)
1,メインのスレッド「MainOperation」でループ
マウスの位置、クリックを取得し、もし左クリックが
押されたらその位置に同心円を序々に表示。
右クリックならポップアップメニューを表示する
為に「WM_NULL」をSendMessage。
2,コールバック関数「MainWndProc()」で
そのWM_NULLを拾ったらShowPopUpMenu()をコール。
3,ShowPopUpMenu()ではTrackPopupMenuで
ポップアップメニューを表示し、その戻り値を
popUpMenuRetというグローバル変数に入れ、
SelectCasePopUpMenu()でその値を元に
それぞれの色に変更。
4,色の変更処理が終わったらメインスレッドの
SendMessageをした次の命令からメインループ再開。
となります。
この方法でとりあえずは出来たのですが、
問題は「For-Nextで同心円を20個描画している最中は
右クリックの取得判定をしていないのでポップアップが
表示されない」ということです。
もちろん単にFor-Nextのループ中にも
GetAsyncKeyState()などを置いてやってキメ細かく取れば
いいようなものですが、やっぱりそれでは根本的な解決に
ならないので他のやり方を試すことにしました。
それが次のプログラムです。
あったときはメインスレッドの状態にかかわらず、
必ずMainWnd_RButtonDown()へ飛ぶ。
そこでメインスレッドを一時停止させて
ポップアップメニューを表示させる。
ポップアップメニューの戻り値はpopUpMenuRetに入れ、
また右クリックが押されたということをスレッド内で
把握できるように「myMainWndMsg」というグローバル変数を
用意し、それにWM_RBUTTONDOWNを入れておく。
スレッドを再開させる。
再開されたスレッドの中では、myMainWndMsgを参照し
何かメッセージがあればSelectCaseMyMainWndMsg()へ飛ぶ。
メッセージがWM_RBUTTONDOWNであれば
ポップアップの戻り値の分岐であるSelectCasePopUpMenu()へ。
SelectCasePopUpMenu()で色を変更。
すべて変更が終わってメインループへ戻る。
となっています。
これで、初めの指針に沿った動作はしてくれるのですが、
一つ気になるのが「右クリックを押してポップアップメニューが
表示されたとき、(マウスポインタを動かして)更にそこで右クリックを
押した時の動作」です。
エクスプローラなどでは新しい位置で改めてポップアップが立ち上がりますが、
このプログラムではそうはならず、ポップアップメニューは元の位置に
表示されたままです。
イベントでMainWnd_RButtonDown()へ飛んだあと、そのサブ内で
処理中にさらに右クリック・ダウンが発生した場合、処理はどうなるのでしようか?
(自分で試したみたところでは、改めてMainWnd_RButtonDown()が呼ばれる
ているようです。)
その場合、このプログラムではSuspendThreadでスレッドを止めています。
サスペンドカウンタはどうなるのでしょうか。
(これも実際に取って見ましたが、まず最初の右クリックで
呼び出し前のサスペンド カウントの値=0が返ります。それから更に
右クリックすると今度は1が返ります。ただしそれ以降何度右クリック
してもサスペンドカウンタは1のままです。)
このあたりが良く分からずにいます。宜しければ御教授下さい。
プログラムのほうでも、「こう組んだ方がいい」というのがありましたら
是非教えてください。
長くなってすみません。よろしくお願いします。
----------------------------------------------------------
この返信を書き終えた後にTomorrowさんのレクを確認してしまったので
ちょっと間が抜けた感じになってしまいましたが、改めて、
Tomorrowさん、レスありがとうございます。
教えていただいた通り第2引数にTPM_RIGHTBUTTONを
セットしてやってみたら、ポップアップは望んだ通りの動きを
してくれました。しかもこれを設定すると右クリックを何度
押してもスレッドカウンタは0のまま変わらず、安心して呼び出せる
ようにもなりました。(何故かはまだ分かってないのですが…)
ありがとうございました。またよろしくお願いします。
手間取ってしまい返信を遅らせてしまいました。すみません。
メイン部分のソースを書きます。
プログラムは「マウスで左クリックした所を中心に、同心円を
外側へ向かって20個、さざ波が広がるようにゆっくり描いていく」
というものです。
右クリックでポップアップメニューを表示して、その選択により
描画色を変える。(ポップアップには「赤に変更」、「緑に変更」、
「青に変更」、の3つのメニュー項目)
ただし、もし20個の同心円の描画中に色が変えられた場合、
今描画している20個はそのままの色で描き、色の変更は次回から
反映させるものとする。
(プログラムはAB 4.04のプロジェクトで作っています)
ソースリスト1(ここをクリック) [ここをクリックすると内容が表示されます]
主な流れは、コード: 全て選択
'-------------------------------------------------------------
' ウィンドウメッセージを処理するためのコールバック関数
Function MainWndProc(hWnd As DWord, dwMsg As DWord, wParam As DWord, lParam As DWord) As DWord
' TODO: この位置にウィンドウメッセージを処理するためのコードを記述します。
'WM_NULLを受け取るとポップアップメニューを表示
If dwMsg=WM_NULL Then ShowPopUpMenu()
' イベントプロシージャの呼び出しを行います。
MainWndProc=EventCall_MainWnd(hWnd,dwMsg,wParam,lParam)
End Function
'
'●メイン・スレッド
'
Function MainOperation(dwDummy As DWord)
While 1
'↓マウスポインタの位置、左右クリックを取得
GetMousePositionAndClick()
'↓右クリックが押されたら「WM_NULL」をSendMessage
If rightClickDown Then
SendMessage(hMainWnd,WM_NULL,0,0)
'↑初めはここで直接「TrackPopUpMenu」を実行したが
'それではポップアップは表示されなかった
End If
'↓左クリックが押されたらその位置に同心円を外側に向かって表示していく
If leftClickDown Then
Dim I As Long
For I=0 to 19
DrawCircle (hMemDC,mousePosition.x,mousePosition.y,4+I*2)
InvalidateRect(hMainWnd,ByVal NULL,FALSE)
Sleep (100)
Next I
End If
Wend
End Function
'
'●右クリック・ダウンでのポップアップメニューの表示
'
Sub ShowPopUpMenu()
Dim cPos As POINTAPI
'↓右クリックメニューの表示
GetCursorPos(cPos)
popUpMenuRet=TrackPopupMenu(hMenu1,TPM_LEFTALIGN Or TPM_TOPALIGN Or TPM_RETURNCMD,cPos.x,cPos.y,0,hMainWnd,ByVal NULL)
'↓ポップアップメニューの戻り値があるならSelectCasePopUpMenu()へ
If popUpMenuRet Then SelectCasePopUpMenu()
'↓左クリックがUpになるまで待つ
'(これを待たないとメニュー選択時の左クリックをメインスレッドで
'拾ってしまい、そこで円を描いてしまう)
'ただし、時々左クリックがUpになっていないのにこの判定ループを
'すり抜ける時がある
While GetAsyncKeyState(1) And &H8000
Wend
End Sub
'
'●右クリックポップアップメニューの分岐
'
Sub SelectCasePopUpMenu()
Select Case popUpMenuRet
Case 1000
penColor=RGB(255,0,0) ':赤に変更
Case 1001
penColor=RGB(0,255,0) ':緑に変更
Case 1002
penColor=RGB(0,0,255) ':青に変更
End Select
popUpMenuRet=0
End Sub
1,メインのスレッド「MainOperation」でループ
マウスの位置、クリックを取得し、もし左クリックが
押されたらその位置に同心円を序々に表示。
右クリックならポップアップメニューを表示する
為に「WM_NULL」をSendMessage。
2,コールバック関数「MainWndProc()」で
そのWM_NULLを拾ったらShowPopUpMenu()をコール。
3,ShowPopUpMenu()ではTrackPopupMenuで
ポップアップメニューを表示し、その戻り値を
popUpMenuRetというグローバル変数に入れ、
SelectCasePopUpMenu()でその値を元に
それぞれの色に変更。
4,色の変更処理が終わったらメインスレッドの
SendMessageをした次の命令からメインループ再開。
となります。
この方法でとりあえずは出来たのですが、
問題は「For-Nextで同心円を20個描画している最中は
右クリックの取得判定をしていないのでポップアップが
表示されない」ということです。
もちろん単にFor-Nextのループ中にも
GetAsyncKeyState()などを置いてやってキメ細かく取れば
いいようなものですが、やっぱりそれでは根本的な解決に
ならないので他のやり方を試すことにしました。
それが次のプログラムです。
ソースリスト2(ここをクリック) [ここをクリックすると内容が表示されます]
右クリックはイベントとして取得し、右クリックがコード: 全て選択
'
'●メイン・スレッド
'
Function MainOperation(dwDummy As DWord)
While 1
'↓マウスポインタの位置、左右クリックを取得
GetMousePositionAndClick()
'↓何らかのウインドウメッセージがあったならそれに対処
If myMainWndMsg Then
SelectCaseMyMainWndMsg()
myMainWndMsg=0
End If
'↓左クリックが押されたらその位置に同心円を外側に向かって表示していく
If leftClickDown Then
Dim I As Long
For I=0 to 19
DrawCircle (hMemDC,mousePosition.x,mousePosition.y,4+I*2)
InvalidateRect(hMainWnd,ByVal NULL,FALSE)
Sleep (100)
Next I
End If
Wend
End Function
'
'●ウインドウメッセージによる分岐
'
Sub SelectCaseMyMainWndMsg()
Select Case myMainWndMsg
Case WM_RBUTTONDOWN
SelectCasePopUpMenu()
End Select
End Sub
'
'●右クリックポップアップメニューの分岐
'
Sub SelectCasePopUpMenu()
Select Case popUpMenuRet
Case 1000
penColor=RGB(255,0,0) ':赤に変更
Case 1001
penColor=RGB(0,255,0) ':緑に変更
Case 1002
penColor=RGB(0,0,255) ':青に変更
End Select
popUpMenuRet=0
End Sub
'
'●右クリックダウン・イベント
'
Sub MainWnd_RButtonDown(flags As Long, x As Integer, y As Integer)
'スレッドの一時停止
SuspendThread(hMainThread)
'↓マウスポインタがクライアントエリア内ならポップアップメニューを表示
If mousePosition.x>=0 And mousePosition.x<clientRc.right And mousePosition.y>=0 And mousePosition.y<clientRc.bottom Then
'ポップアップメニューの表示
ShowPopUpMenu()
'メインスレッド内で扱えるようにウインドウメッセージを格納
myMainWndMsg=WM_RBUTTONDOWN
End If
'スレッドの再開
ResumeThread(hMainThread)
End Sub
'
'●右クリックダウンでのポップアップメニューの表示
'
Sub ShowPopUpMenu()
Dim cPos As POINTAPI
'↓右クリックメニューの表示
GetCursorPos(cPos)
popUpMenuRet=TrackPopupMenu(hMenu1,TPM_LEFTALIGN Or TPM_TOPALIGN Or TPM_RETURNCMD,cPos.x,cPos.y,0,hMainWnd,ByVal NULL)
'↓左クリックがUpになるまで待つ
'(これを待たないとメニュー選択時の左クリックをメインスレッドで
'拾ってしまい、そこで円を描いてしまう)
'ただし、時々左クリックがUpになっていないのにこの判定ループを
'すり抜ける時がある
While GetAsyncKeyState(1) And &H8000
Wend
End Sub
あったときはメインスレッドの状態にかかわらず、
必ずMainWnd_RButtonDown()へ飛ぶ。
そこでメインスレッドを一時停止させて
ポップアップメニューを表示させる。
ポップアップメニューの戻り値はpopUpMenuRetに入れ、
また右クリックが押されたということをスレッド内で
把握できるように「myMainWndMsg」というグローバル変数を
用意し、それにWM_RBUTTONDOWNを入れておく。
スレッドを再開させる。
再開されたスレッドの中では、myMainWndMsgを参照し
何かメッセージがあればSelectCaseMyMainWndMsg()へ飛ぶ。
メッセージがWM_RBUTTONDOWNであれば
ポップアップの戻り値の分岐であるSelectCasePopUpMenu()へ。
SelectCasePopUpMenu()で色を変更。
すべて変更が終わってメインループへ戻る。
となっています。
これで、初めの指針に沿った動作はしてくれるのですが、
一つ気になるのが「右クリックを押してポップアップメニューが
表示されたとき、(マウスポインタを動かして)更にそこで右クリックを
押した時の動作」です。
エクスプローラなどでは新しい位置で改めてポップアップが立ち上がりますが、
このプログラムではそうはならず、ポップアップメニューは元の位置に
表示されたままです。
イベントでMainWnd_RButtonDown()へ飛んだあと、そのサブ内で
処理中にさらに右クリック・ダウンが発生した場合、処理はどうなるのでしようか?
(自分で試したみたところでは、改めてMainWnd_RButtonDown()が呼ばれる
ているようです。)
その場合、このプログラムではSuspendThreadでスレッドを止めています。
サスペンドカウンタはどうなるのでしょうか。
(これも実際に取って見ましたが、まず最初の右クリックで
呼び出し前のサスペンド カウントの値=0が返ります。それから更に
右クリックすると今度は1が返ります。ただしそれ以降何度右クリック
してもサスペンドカウンタは1のままです。)
このあたりが良く分からずにいます。宜しければ御教授下さい。
プログラムのほうでも、「こう組んだ方がいい」というのがありましたら
是非教えてください。
長くなってすみません。よろしくお願いします。
----------------------------------------------------------
この返信を書き終えた後にTomorrowさんのレクを確認してしまったので
ちょっと間が抜けた感じになってしまいましたが、改めて、
Tomorrowさん、レスありがとうございます。
教えていただいた通り第2引数にTPM_RIGHTBUTTONを
セットしてやってみたら、ポップアップは望んだ通りの動きを
してくれました。しかもこれを設定すると右クリックを何度
押してもスレッドカウンタは0のまま変わらず、安心して呼び出せる
ようにもなりました。(何故かはまだ分かってないのですが…)
ありがとうございました。またよろしくお願いします。
GetAsyncKeyStateではどうしても「取りこぼし」が発生する可能性を無くすことができません。
Windowsはイベント駆動(ドリブン)型ですから,それに沿ったプログラムの作りを念頭に置かれています。
つまりマウスボタンのクリックはOnLButtonUpやOnRButtonUpなどのイベントで検出するようにする方が確実です。
その場合RButtonUpイベントではTrackPopupMenuを(TPM_RETURNCMDを指定して)呼ぶと共に,
その戻り値でSelectCasePopUpMenu相当のことを行えるはずです。
左ボタンクリックはLButtonUpイベントで検出しますが,それをどうにかして「メインスレッド」へ伝えればよいのです。
それにはやっぱりメッセージが楽です。スレッドを作ったときに得られるIDを元にPostThreadMessageでメッセージを投げることができます。
そして「メインスレッド」では,たとえばこういう風にしてそのメッセージを受け取ったら動作するようにします。
なお,排他制御は話を簡単にするため行っていません。
ところで今まで「メインスレッド」と書きましたが,
普通はメインスレッドと言うとCreateThreadなどで後から作ったスレッドではなく,
アプリケーションの実行を開始するときから実行を始めるスレッドの事を指します。
ウィンドウプログラムではウィンドウを作ってメッセージループに入る流れがメインスレッドの主な動作です。(ABでRADを使ったときもそうなっています)
Windowsはイベント駆動(ドリブン)型ですから,それに沿ったプログラムの作りを念頭に置かれています。
つまりマウスボタンのクリックはOnLButtonUpやOnRButtonUpなどのイベントで検出するようにする方が確実です。
その場合RButtonUpイベントではTrackPopupMenuを(TPM_RETURNCMDを指定して)呼ぶと共に,
その戻り値でSelectCasePopUpMenu相当のことを行えるはずです。
左ボタンクリックはLButtonUpイベントで検出しますが,それをどうにかして「メインスレッド」へ伝えればよいのです。
それにはやっぱりメッセージが楽です。スレッドを作ったときに得られるIDを元にPostThreadMessageでメッセージを投げることができます。
コード: 全て選択
Sub MainWnd_LButtonUp(flags As Long, x As Integer, y As Integer)
PostThreadMessage(threadID, WM_LBUTTONUP, flags, MAKELONG(x, y)) 'threadIDはグローバル変数にしておく
End Sub
コード: 全て選択
Function MainOperation(dummy As DWord) As DWord
While 1
Dim msg As MSG
Dim ret As Long
ret = GetMessage(msg, 0, 0, 0)
If ret = 0 Or ret = -1 Then
MainOperation = msg.wParam
Exit Function
End If
If msg.message = WM_LBUTTONUP
Dim x As Long, y As Long
x = LOWORD(msg.lParam)
y = HIWORD(msg.lParam)
Dim I As Long
For I = 0 to 19
DrawCircle(hMemDC, x, y, 4 + I * 2)
InvalidateRect(hMainWnd, ByVal NULL, FALSE)
Sleep(100)
Next I
End If
Wend
End Function
ところで今まで「メインスレッド」と書きましたが,
普通はメインスレッドと言うとCreateThreadなどで後から作ったスレッドではなく,
アプリケーションの実行を開始するときから実行を始めるスレッドの事を指します。
ウィンドウプログラムではウィンドウを作ってメッセージループに入る流れがメインスレッドの主な動作です。(ABでRADを使ったときもそうなっています)
レクありがとうございます。
レクありがとうございます。
返信遅れてすみません。
「PostThreadMessage」,「GetMessage」
スレッドに対するメッセージの受け渡し方法
勉強になりました。(「メインスレッド」も)
それから、
初め左クリックも「Down」イベントで取っていて、
そのときはクリック反応がうまく取れたり取れなかったりで、
何故なんかなと思いながら、GetAsyncKeyStateに変えました。
言われてみればクリックが押されてなければ「Up」は
あり得ないとも思えます。
下のような簡単なコードで実験したところ、
カチカチとクリックする度に
常に、"Left Down"と"Left Up"がセットで表示される
筈だと思ったのですが、必ずしもそうではなく、
"Left Down"はしばしば表示されないことも
ありました。
ところが"Left Up"はクリック毎に確実に表示される。
そう思って改めて見直すと、ポップアップメニューの選択時
などもボタンが「放された」時に決定されているようで、
ここらへんの理由はハッキリ自分にはわかりませんが
(Upの信号がDownの信号に被さってしまうのかなとか、
勝手にありこれ考えますが…)
いずれにしても以降、「Up」にコードを書くようにしたいと思います。
困った点が二つ出てきました。
一つは、もし設計として「同心円を描画中の左クリックは無視する」と
するなら、どうするべきか。
このままだと同心円を描画中も左クリックを拾ってしまい、カチカチと
クリックを繰り返すと後から後からその位置に円が描画されてしまう。
左クリックのメッセージが「メッセージキュー」に溜まるためか、
と思ったのですが、そこからの解決が中々分からず。
もう一つは、
ポップアップメニューを開いて、でも"やっぱりやめた"と何も選択しない場合
メニュー領域以外を左クリックしてクリアする時があります。
自分のソースではそのときの左クリックを「同心円の描画ポイント」として
取ってしまいそこで描画を開始してしまう。
これも設計として「ポップアップメニューのクリア時の左クリックは同心円の
描画ポイントとして扱わない」とするなら、どうするべきか。
メニューで何か選択したときは例によってクリックが「Up」になるまで
TrackPopUpMenuの制御の下のようで大丈夫なのですが、
クリアするときの左クリックはDownされたときに処理が戻ってくるようで
そこから改めてUpになると、「新たな左クリック」と認識されてしまいます。
これがどうにも、その違いを区別させることが難しくて。
また初めのコードのMainWnd_RButtonDown()内でやってたように
とするのは、あんまり良くないような。
ズルズル聞いてしまってすみません。
もし解決の方法、ヒントがあれば教えてください。
返信遅れてすみません。
「PostThreadMessage」,「GetMessage」
スレッドに対するメッセージの受け渡し方法
勉強になりました。(「メインスレッド」も)
それから、
「Down」でなくて「Up」なんですね。つまりマウスボタンのクリックはOnLButtonUpやOnRButtonUpなどの
イベントで検出するようにする方が確実です。
初め左クリックも「Down」イベントで取っていて、
そのときはクリック反応がうまく取れたり取れなかったりで、
何故なんかなと思いながら、GetAsyncKeyStateに変えました。
言われてみればクリックが押されてなければ「Up」は
あり得ないとも思えます。
下のような簡単なコードで実験したところ、
コード: 全て選択
Sub MainWnd_LButtonDown(flags As Long, x As Integer, y As Integer)
Print "Left Down"
End Sub
Sub MainWnd_LButtonUp(flags As Long, x As Integer, y As Integer)
Print "Left Up"
End Sub
常に、"Left Down"と"Left Up"がセットで表示される
筈だと思ったのですが、必ずしもそうではなく、
"Left Down"はしばしば表示されないことも
ありました。
ところが"Left Up"はクリック毎に確実に表示される。
そう思って改めて見直すと、ポップアップメニューの選択時
などもボタンが「放された」時に決定されているようで、
ここらへんの理由はハッキリ自分にはわかりませんが
(Upの信号がDownの信号に被さってしまうのかなとか、
勝手にありこれ考えますが…)
いずれにしても以降、「Up」にコードを書くようにしたいと思います。
新しいソース(ここをクリック) [ここをクリックすると内容が表示されます]
それで上の様にソースを変えたのですがコード: 全て選択
'
'●メインスレッド改め「スレッド1」
'
Function MainOperation(dwDummy As DWord)
While 1
Dim msg As MSG
Dim ret As Long
ret = GetMessage(msg, 0, 0, 0)
If ret = 0 Or ret = -1 Then
MainOperation = msg.wParam
Exit Function
End If
If msg.message=WM_RBUTTONUP And popUpMenuRet<>0 Then
SelectCasePopUpMenu()
End If
If msg.message = WM_LBUTTONUP Then
Dim x As Long, y As Long
x = LOWORD(msg.lParam)
y = HIWORD(msg.lParam)
Dim I As Long
For I = 0 to 19
DrawCircle(hMemDC,x,y,4+I*2)
InvalidateRect(hMainWnd,ByVal NULL,FALSE)
Sleep(100)
Next I
End If
Wend
End Function
困った点が二つ出てきました。
一つは、もし設計として「同心円を描画中の左クリックは無視する」と
するなら、どうするべきか。
このままだと同心円を描画中も左クリックを拾ってしまい、カチカチと
クリックを繰り返すと後から後からその位置に円が描画されてしまう。
左クリックのメッセージが「メッセージキュー」に溜まるためか、
と思ったのですが、そこからの解決が中々分からず。
もう一つは、
ポップアップメニューを開いて、でも"やっぱりやめた"と何も選択しない場合
メニュー領域以外を左クリックしてクリアする時があります。
自分のソースではそのときの左クリックを「同心円の描画ポイント」として
取ってしまいそこで描画を開始してしまう。
これも設計として「ポップアップメニューのクリア時の左クリックは同心円の
描画ポイントとして扱わない」とするなら、どうするべきか。
メニューで何か選択したときは例によってクリックが「Up」になるまで
TrackPopUpMenuの制御の下のようで大丈夫なのですが、
クリアするときの左クリックはDownされたときに処理が戻ってくるようで
そこから改めてUpになると、「新たな左クリック」と認識されてしまいます。
これがどうにも、その違いを区別させることが難しくて。
また初めのコードのMainWnd_RButtonDown()内でやってたように
コード: 全て選択
'↓左クリックがUpになるまで待つ
'ただし、時々左クリックがUpになっていないのにこの判定ループを
'すり抜ける時がある
While GetAsyncKeyState(1) And &H8000
Wend
ズルズル聞いてしまってすみません。
もし解決の方法、ヒントがあれば教えてください。
UpかDownかは,私も根拠があってのことではなく,単にUpで反応するものが多かったからと言うだけでUpを選んでいます。
描画中に左クリックされた場合の対処は,描画中は左クリックされてもメッセージを投げないと言う風にすればよいです。
たとえばこんなグローバル変数を用意し,
描画中であることを記録させます。
あとは大体想像が付くとは思いますが,こんな風に描画中でないときだけ投げると言う事が実現できると思います。
もう1つのメニューキャンセル時は,今まで考えたことがありませんでした。
メニューが表示されていようとお構いなく左クリックを受け付けるという挙動が一般的ですから。
描画中に左クリックされた場合の対処は,描画中は左クリックされてもメッセージを投げないと言う風にすればよいです。
たとえばこんなグローバル変数を用意し,
コード: 全て選択
Dim IsDrawing As Long
コード: 全て選択
If msg.message = WM_LBUTTONUP Then
IsDrawing = TRUE
' 中略
IsDrawing = FALSE
End If
コード: 全て選択
Sub MainWnd_LButtonUp(flags As Long, x As Integer, y As Integer)
If IsDrawing = FALSE Then
PostThreadMessage(threadID, WM_LBUTTONUP, flags, MAKELONG(x, y))
End If
End Sub
メニューが表示されていようとお構いなく左クリックを受け付けるという挙動が一般的ですから。
レクありがとうございます。
素早いレスありがとうございます。
今度はMainWnd_LButtonUp()に向けて描画中のフラグ
を立ててやるんですね。
それから、メニューキャンセルですが、
このプログラムではやっぱりそれが出来ないとなると
かなり不便になってしまうので、もうちょっと考えて
みようと思います。
また何かいい方法があったらその時は是非教えてください。
長々と聞いてしまいましたが、ありがとうございました。
プログラムも丁寧に書いて貰って。参考にさせてもらいます。
またよろしくお願いします。
今度はMainWnd_LButtonUp()に向けて描画中のフラグ
を立ててやるんですね。
それから、メニューキャンセルですが、
このプログラムではやっぱりそれが出来ないとなると
かなり不便になってしまうので、もうちょっと考えて
みようと思います。
また何かいい方法があったらその時は是非教えてください。
長々と聞いてしまいましたが、ありがとうございました。
プログラムも丁寧に書いて貰って。参考にさせてもらいます。
またよろしくお願いします。
Re: レクありがとうございます。
メニュー自体にキャンセルする項目をつけるのはどうですか?それから、メニューキャンセルですが、
このプログラムではやっぱりそれが出来ないとなると
かなり不便になってしまうので、もうちょっと考えて
みようと思います。
また何かいい方法があったらその時は是非教えてください。
こんな風に↓
コード: 全て選択
――――――――
| 赤に変更 |
| 緑に変更 |
| 青に変更 |
|―――――――|
| キャンセル |
――――――――
レスありがとうございます。
レスありがとうございます。
僕もそれを考えたんですが、ただ
ポップアップのキャンセル項目をクリックしてクリアした場合と
領域外クリックでクリアした場合で
動作が異なってくるので、迷っていました。
それで結局、まどろっこしい方法なんですが
別のやり方で試してみました。
1,まずポップアップメニューの戻り値である
グローバル変数「popUpMenuRet」をあらかじめ「-1」にセット
2,ポップアップを領域外クリックでクリアした場合
popUpMenuRetに「0」がセットされて戻ってくるので
左クリックイベントのMainWnd_LButtonUp()の中では
「もしpopUpMenuRetが0なら
・左クリックUPのメッセージをスレッド1にポストしない
・popUpMenuRet=-1」
と書く。
この方法で何とか実行させました。
あまりスマートな方法じゃないなーと思ってはいるんですが。
Tomorrowさん、レスありがとうございます。
また何かいいアイデアがあったら教えてください。
僕もそれを考えたんですが、ただ
ポップアップのキャンセル項目をクリックしてクリアした場合と
領域外クリックでクリアした場合で
動作が異なってくるので、迷っていました。
それで結局、まどろっこしい方法なんですが
別のやり方で試してみました。
1,まずポップアップメニューの戻り値である
グローバル変数「popUpMenuRet」をあらかじめ「-1」にセット
2,ポップアップを領域外クリックでクリアした場合
popUpMenuRetに「0」がセットされて戻ってくるので
左クリックイベントのMainWnd_LButtonUp()の中では
「もしpopUpMenuRetが0なら
・左クリックUPのメッセージをスレッド1にポストしない
・popUpMenuRet=-1」
と書く。
この方法で何とか実行させました。
あまりスマートな方法じゃないなーと思ってはいるんですが。
Tomorrowさん、レスありがとうございます。
また何かいいアイデアがあったら教えてください。