ページ 1 / 1
TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月02日(金) 04:09
by Ma
右クリックで表示される、ポップアップメニューを、WinAPIを使ってDXライブラリを使ったプログラムに実装しました。
void DisplayPopUpMenu(int menu){
//ウィンドウ位置取得
int x,y;
GetWindowPosition(&x,&y);
switch(menu){
case MAIN_POPUP:
{
TrackPopupMenu(mainPopUpMenu, TPM_LEFTALIGN,MousePosX+x, MousePosY+y, 0, mainWindowHandle, NULL);
}
その表示部分は↑のとおりです。
このプログラムは期待通りに動作しましたが、この関数は制御を戻してくれないので(BGM再生中などに右クリックすると困る)、別スレッドで呼ぼうとおもったんです。
改良後、
DWORD WINAPI DisplayPopUpMenuNewThread(LPVOID vdParam) {
//ウィンドウ位置取得
int x,y;
GetWindowPosition(&x,&y);
int value = TrackPopupMenu(mainPopUpMenu, TPM_LEFTALIGN,MousePosX+x, MousePosY+y, 0, mainWindowHandle, NULL);
DWORD err = (GetLastError());
return -1;
}
HANDLE popUpThread;
void DisplayPopUpMenu(int menu){
switch(menu){
case MAIN_POPUP:
{
//クリックした場所に表示。
DWORD thID = NULL;
popUpThread = (HANDLE)CreateThread(NULL,0,DisplayPopUpMenuNewThread,NULL,0, &thID);
}
こんな感じになったんですが、ポップアップメニューが表示されませんでした。
GetLastErrorで取得したエラーコードは、err = 87 となり、エラー内容、パラメターが正常ではないとのことなんですが、特に間違っているところがみあたりません。
(value = 0 でした。これはエラー発生を意味します。)
そもそも、別スレッドで、ポップアップメニューを呼ぶのは反則だということなのでしょうか。。。。?

Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月03日(土) 09:54
by Ma
検証用プロジェクトを作りました。(Win32)
(VC++ 2008 Express)
検証にお使いください。
どこぞのプロジェクトソースをコピーペーストしてちょっといじっただけなので、
検証に不要な部分が多少ありますが、あまり気にしないでください。
なお、ソースコードの
//これをコメントアウトするかどうかで切り替え可能。
//#define TRY_NEW_THREAD
の部分を
//これをコメントアウトするかどうかで切り替え可能。
#define TRY_NEW_THREAD
こうすると、新規スレッドを使います。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月03日(土) 22:18
by Justy
その手のはスレッドと紐付いているので、別スレッドでは処理できなかったはずです。
別スレッドで行うのであれば、そのスレッド内で別の(見えない)ウインドウを作って
そのウインドウに対してポップアップメニューを出せば非同期で出すことができると思います。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 00:20
by Ma
>Justy さん
なるほど、実際にやってみたら非同期的に表示することに成功しました。
ありがとうございます^^
それで、新しく問題がでてきました。
今度は、メニューを表示している間、もう一度他の場所を右クリックした場合、もう一度新しい場所にポップアップメニューを表示させたかったんですが、これがうまくいきません。
強制的にTrackPopupMenuを終了させる方法が分からない(たぶんない?)ので、メインスレッドからキャンセルさせる方法は諦めました。
(あるようならぜひおしえてください。)
次に、本来制御を戻さないのにキャンセルできるのは、デフォルトウィンドウプロシージャがやる仕事だからなのだろうと勝手に思いまして(あってるか不明…)、見えないウィンドウにも新しく作ったウィンドウプロシージャを呼ばせようと思ったのですが、今度は、このウィンドウプロシージャは呼ばれず、しかも処理が極端に遅くなってしまいました。
(特によくわからないのはブレイクポイントをいれるとポップアップメニューが表示されて、いれないと表示されないという珍現象…)
おそらく、この方法でいけるとおもうのですが、コーディングミスのせいなのか失敗でした。
(よろしければ、添付ファイルをごらんになってください。)
どうしたらポップアップのキャンセルと別の場所にメニュー表示が上手くいくのか教えてもらえないでしょうか?

Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 02:16
by Justy
>強制的にTrackPopupMenuを終了させる方法が分からない
TrackPopupMenuの前に SetForegroundWindowを呼んでおくとキャンセルできます。
ソースを見ました。
スレッドが常駐しているのですね。
んー、とりあえず今のソースは残しておいた上で、元の随時スレッドを作るタイプに戻し
そのスレッドの中で
・ ウインドウ作成
・ 通常の GetMessage~DispatchMessage()のループ
・ そのウインドウのプロシージャの WM_CREATEで SetForegroundWindowと TrackPopupMenuを呼び、
TrackPopupMenuの処理から戻ってきたらDestroyWindowを呼ぶ。
とします。
多分これで仕組み的にはうまくいくような気がしますが、どうでしょう?
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 07:55
by Ma
アドバイスにそってやってみましたが、二つ目のウィンドウプロシージャが呼ばれません。
二つ目作るのは初めてなので何がいけないのかよくわかりません。
どうでしょうか?何か思いつく点などないでしょうか?
あと、SetForegroundWindowについては、まだ未実装です。
(忘れてただけですけどもw)
ウィンドウプロシージャが解決次第やります。

Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 12:20
by Justy
1つ目と2つ目のウインドウクラス名が同じになっていますよ。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 13:18
by Ma
>1つ目と2つ目のウインドウクラス名が同じになっていますよ。
ウィンドウクラス名はかぶっちゃいけないんですね…。
できましたー!
ありがとうございます。
ただ、この場合、TerminateThread 関数を使わないと、TrackPopupMenu を終了させることができないのでしょうか?
HANDLE secondThread = NULL;
DWORD thID = NULL;
void popupMenu()
{
POINT pt;
pt.x = LOWORD(lParameter);
pt.y = HIWORD(lParameter);
ClientToScreen(hWindow,&pt);
#ifdef TRY_NEW_THREAD
//第二スレッドを開始します。
if(secondThread != NULL){
DWORD dwExCode;
GetExitCodeThread(secondThread, &dwExCode);
if(dwExCode == STILL_ACTIVE){
WPARAM wParam = NULL;
TerminateThread(secondThread,dwExCode);
}
}
secondThread = CreateThread(NULL,0,DisplayPopUpMenuNewThread,NULL,0, &thID);
}
こんな感じの実装で、おおむね期待通りに動いたのですが、TerminateThread は危険な関数で極力避けたほうがいいとあったのですが、
代替できる方法はないでしょうか?
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 14:21
by Justy
>TerminateThread 関数を使わないと、TrackPopupMenu を終了させることができないのでしょうか
それは上の説明の通り、WM_CREATEで SetForegroundWindow / DestroyWindowを使っても
終わらせられなかった、ということですか?
(DestroyWindowも WM_CREATEの中ですよ)
元々はDXライブラリと併用するつもり、なのですよね?
一応上の方法で可能といえば可能なはずですが今の方法だと、アクティブなウインドウが
変わってしまうとか、ポップアップメニューが表示されるまで時間がかかることがある、など
(いろいろ手を尽くせばなんとかなるのかもしれませんが)少々問題がありそうです。
ここまで引っ張っておいて何ですが、メインの処理を止めたくないのなら面倒でも
TrackPopupMenuと同じ機能をDXライブラリの機能を使って実装・システム化した方が余計なことに
悩まなくて済みますし、何かと拡張しやすいのではないでしょうか。
或いは、TrackPopupMenuを使うにしても処理が止まってもいいような内容にするとか。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 15:03
by Ma
> それは上の説明の通り、WM_CREATEで SetForegroundWindow / DestroyWindowを使っても
>終わらせられなかった、ということですか?
そうです。
そもそも今のコードだと選択しない限りTrackPopupMenu から制御が戻らないので
> そのウインドウのプロシージャの WM_CREATEで SetForegroundWindowと TrackPopupMenuを呼び、
> TrackPopupMenuの処理から戻ってきたらDestroyWindowを呼ぶ。
の方法では、DestroyWindow までたどり着きません。
というのは、TrackPopupMenuで表示後、(メニュー項目以外の)適当な場所をクリックしても制御が戻らないんです。(キャンセルができない理由は自分はよくわかっていません)
それゆえにDestroyWindowまでたどり着きません。
結果的に、いろんなところを右クリックするとおなじポップアップメニューが乱立します。
(他の場所をクリックしてもTrackPopupMenu が、自動的にキャンセルされないため。)
だから、TerminateThread で、TrackPopupMenu を強制的にキャンセルさせたってわけなんです。
> ここまで引っ張っておいて何ですが、メインの処理を止めたくないのなら面倒でも
>TrackPopupMenuと同じ機能をDXライブラリの機能を使って実装・システム化した方が余計なことに
>悩まなくて済みますし、何かと拡張しやすいのではないでしょうか。
> 或いは、TrackPopupMenuを使うにしても処理が止まってもいいような内容にするとか。
そうですねー。前者はたしかにそれもありかもしれないですね…。
最初に自作するよりは手軽に実装できると思って始めたので、できれば今のままの方法がいいんです。
とはいえ、困難になったら自作に切り替えるべきでしょうね。
ただ、Windows Media Player などのソフトで右クリックすると、音楽は正常再生、メーター?も正常に進んでいるので、(大企業がやっているぐらいなので)安全な方法はあると思うんです。

Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 15:56
by Justy
>そうです
・ win32_test_a.cpp
最小限で書き換えてみました。
とりあえずこちらでは動いています。
・ win32_test_b.cpp
メニューが表示されるまで時間が少しかかっていたので、毎回スレッドを作らない方式に
直してみました。これもこちらでは動いています。
あれ、この方式だと予想以上にうまくいっている気が。
>Windows Media Player などのソフトで右クリックすると
wmpがどうやっているかはわかりませんが、TrackPopupMenu 中でもWM_TIMERとかのメッセージは
普通に飛んできます。
この中で処理すればスレッドを使わなくても何かしらの処理を行うことはできますね
(DXライブラリでその方法が使えるかどうかはわかりません)
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 22:05
by Ma
> win32_test_a.cpp
あれ、ソースが同じなのになんで動かなかったんだろう と思ってツール使って比べてみたら、なぜかウィンドウクラス名が元に戻っていて不具合になっていたみたいですorz
確認ミスでしたすいません。
>win32_test_b.cpp
なるほど、SendMessage で上手くいけそうです^^
*解決取り消しました。

Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 23:14
by Ma
すいません、テストしてみたら、メインスレッドのほう、停止していたみたいです。
別スレッドだから当然勝手に動いているだろうと推測していたのが間違いでしたorz
不思議で仕方ない。
確認に使ったソースを添付しました。
bバージョンを元に作ってあります。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月04日(日) 23:47
by Justy
GetMessage関数ははメッセージキューが空であればそこで止まります。
次に SendMessageもプロシージャが投げたメッセージを処理し終わらなければ戻ってきません。
なので、このケースでは添付のように直させばカウントは動きます。
タイマーを避けたいのであれば、GetMessageの代わりに PeekMessageを使って
メッセージを処理してもいいですね。
デッドタイムを使う
http://wisdom.sakura.ne.jp/system/winap ... win46.html
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月05日(月) 01:39
by Ma
*間違えて投稿。

Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月05日(月) 01:41
by Justy
SendMessage -> PostMessage
あと、それだとすごい勢いで whileループが動きますので、Sleepとか何かで
少しウエイトを入れればいいかと。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月05日(月) 02:07
by Ma
>タイマ
なるほど、これも手ですね。
>SendMessage , PostMessage
ついさっき、違いがわからなくて調べたところでした。
>ウェイト
ですねw
Win32で成功したので、次はDXライブラリへと移植に挑戦します。
Re:TrackPopupMenuは、別スレッドから呼び出せない?
Posted: 2010年4月05日(月) 02:10
by Ma
で、できました!
ちょっぴり感動しました><
Justyさん、お付き合いどうもありがとうございました。
今度時間できたら、WinAPIの復習したいとおもいます(汗
