ポップアップメニューについて

解決


ttt  2008-07-31 02:36:30  No: 68803

VS2005 SDKでダイアログベースのアプリを作成しています。
ダイアログ上を右クリックしたらポップアップウィンドウが出現し、
そのメニューを選ぶとタスクトレイに格納し、タスクトレイにあるアイコン
を右クリックするとポップアップウィンドウが出現し、それを選らぶとまたダイアログ
を出現する、ということをしたいのですが、詰まってしまいました。
タスクトレイからダイアログをふたたび出現させる際、ダイアログは再び元に戻るのですが
その際、一緒にポップアップメニュー(hSubMenu1:ダイアログ上で出現する用のメニュー)が
出現します。それは出現したダイアログをマウスで動かすと何もせずに消えます。
さらにダイアログの×を押して閉じる際も同じポップアップメニューが出現します。
ダイアログが閉じて無くなってもそのポップアップメニューは消えずに残ったままになります。

そこでデバッガで追ったところ、どちらの際もダイアログプロージャ内のWM_RBUTTONUPメッセージを
拾っていることがわかりました。
ダイアログからタスクトレイには独自メッセージを定義してますので大丈夫なんですが、逆の時は
ダイアログ側のメッセージ処理にかかってくるのでどうしたらよいか詰まってしまいました。

さらにダイアログを閉じる際がどうしてWM_RBUTTONUPを拾ってしまうのかわかりませんでした。

以上の点を聞きたいと思います。よろしくお願い致します。

// タスクトレイのマウスメッセージ
#define WM_TASKTRAY  (WM_APP + 1)

// メイン
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst,
                   LPSTR lpszCmdLine, int nCmdShow)
{
    DialogBox( hCurInst, "OPTDLG", NULL, (DLGPROC)MyDlgProc );
    return 0;
}

// ダイアログボックス
LRESULT CALLBACK MyDlgProc( HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp)
{
   // 右クリ用
   POINT pos;
   static HMENU hMenu, hSubMenu1, hSubMenu2;
   HINSTANCE hInst;

   // タスクトレイ
   static NOTIFYICONDATA  nid;
   HICON hIcon;

   switch( msg ){
       case WM_INITDIALOG:
       // もろもろを初期化
       // ポップアップメニュー
       hMenu = LoadMenu( (HINSTANCE)GetWindowLong( hDlgWnd, GWL_HINSTANCE ),  MAKEINTRESOURCE(IDR_MENU1) );
       // サブメニューのハンドルを取得する
       hSubMenu1 = GetSubMenu( hMenu, 0 );

       hMenu = LoadMenu( (HINSTANCE)GetWindowLong( hDlgWnd, GWL_HINSTANCE ),  MAKEINTRESOURCE(IDR_MENU2) );
       hSubMenu2 = GetSubMenu( hMenu, 0 );
       
       // アイコン
       hIcon = LoadIcon((HINSTANCE)GetWindowLong( hDlgWnd, GWL_HINSTANCE ), MAKEINTRESOURCE(IDI_ICON1));
       // タスクトレイ
       nid.cbSize = sizeof(nid);
       nid.hWnd   = hDlgWnd;
       nid.uID    = IDI_ICON1;
       nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
       nid.uCallbackMessage = WM_TASKTRAY;
       //nid.hIcon  = hIcon;  
       nid.hIcon  = hIcon;
       strcpy( nid.szTip, "tray" );
       return TRUE;
       
       case WM_COMMAND:
          switch( LOWORD( wp ) ){
                 case IDCANCEL:
                    EndDialog( hDlgWnd, IDCANCEL );
                    break;
                 case ID_POPUPTASK: // タスクトレーに格納
                    Shell_NotifyIcon( NIM_ADD, &nid );  // タスクトレイへの登録
                    // メニューを破棄
                    DestroyMenu(hSubMenu1);

                    hMenu = LoadMenu( (HINSTANCE)GetWindowLong( hDlgWnd, GWL_HINSTANCE ),  MAKEINTRESOURCE(IDR_MENU2) );
                    hSubMenu2 = GetSubMenu( hMenu, 0 );
                    SetMenu( hDlgWnd, NULL );
                    
                    // ウィンドウを消す
                    ShowWindow( hDlgWnd, SW_HIDE );
                    UpdateWindow( hDlgWnd );
                    break;
                case ID_POPUP1END:  // 終了
                    EndDialog( hDlgWnd, 0 );
                    break;
                case ID_RETURN:    // 元に戻す
                    // ウィンドウを元に戻す
                    ShowWindow( hDlgWnd, SW_SHOWNORMAL );
                    SetMenu( hDlgWnd, hSubMenu1 );
                    Shell_NotifyIcon( NIM_DELETE, &nid );

                    // メニューを破棄
                    DestroyMenu(hSubMenu2);
                    hMenu = LoadMenu( (HINSTANCE)GetWindowLong( hDlgWnd, GWL_HINSTANCE ),  MAKEINTRESOURCE(IDR_MENU1) );
                    // サブメニューのハンドルを取得する
                    hSubMenu1 = GetSubMenu( hMenu, 0 );
                    UpdateWindow( hDlgWnd );
                    break;
                case ID_POPUP2END:  // 終了
                    EndDialog( hDlgWnd, 0 );
                    break;
                default:
                    return FALSE;
                 
       case WM_RBUTTONDOWN:  //右クリック
             // カーソルの座標を取得
             pos.x = LOWORD( lp );
             pos.y = HIWORD( lp );

          // 取得したカーソル位置をスクリーン座標に変換する
          ClientToScreen( hDlgWnd, &pos );
 
          SetForegroundWindow( hDlgWnd );
          // ポップアップメニューを表示
          kari = TrackPopupMenu( hSubMenu1, TPM_LEFTALIGN, pos.x, pos.y, 0, hDlgWnd, NULL );
          return FALSE;
       case WM_TASKTRAY:   // タスクトレイ時に
        switch( lp ){
           case WM_RBUTTONDOWN:
               GetCursorPos( &pos );
               SetForegroundWindow( hDlgWnd );
               // ポップアップメニューを表示
               TrackPopupMenu( hSubMenu2, TPM_RIGHTALIGN | TPM_BOTTOMALIGN, pos.x, pos.y, 0, hDlgWnd, NULL );
               // ウィンドウを元に戻す
               //ShowWindow( hDlgWnd, SW_SHOWNORMAL );
               break;
        }
        break;
    default:
        return FALSE;
   }
}


ttt  2008-07-31 02:38:59  No: 68804

書き込みミスがありました。
×  ダイアログプロージャ内のWM_RBUTTONUP
○  ダイアログプロージャ内のWM_RBUTTONDOWN

です。よろしくお願い致します。


仲澤@失業者  2008-07-31 03:14:14  No: 68805

{}の位置が妙ですな。
分岐コードを関数化しましょう。
そうすれば多分バグがわかります。


夏みかん  2008-07-31 07:58:13  No: 68806

> さらにダイアログを閉じる際がどうしてWM_RBUTTONUPを拾ってしまうのかわかりませんでした。
ダイアログを閉じるときに右上の[×]ボタンをマウスでクリックしますよね。
だからこのクリック時にWM_RBUTTONDOWN、WM_RBUTTONUPが発生するので問題ないはずです。
逆にWM_RBUTTONUPが発生しないとクリックを検出できなくなるよ。

ソースを見ましたがこれだけでは良く分かりません。
メニューとダイアログのリソースも必要な部分だけでいいので貼り付けたら分かるかも。

気になった点。
> LRESULT CALLBACK MyDlgProc( HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp)
戻り値は BOOL でしょ。ダイアログだから。

あと無駄に LoadMenu、DestroyMenu、SetMenu、GetSubMenu などが使われているね。
自分なら WM_INITDIALOG 内で LoadMenu、WM_CLOSE 内で DestroyMenu と書く。
そのほか IDCANCEL、ID_POPUP1END、ID_POPUP2END も一箇所(WM_CLOSE)に書く。

下にサンプルを載せておく。
BOOL CALLBACK MyDlgProc( HWND hDlgWnd, UINT msg, WPARAM wp, LPARAM lp )
{
    static HMENU hMenu, hSubMenu1, hSubMenu2;
    static NOTIFYICONDATA nid;
    
    switch ( msg ){
        case WM_INITDIALOG:
        {
            HINSTANCE hInstance = (HINSTANCE)GetWindowLong(hDlgWnd,GWL_HINSTANCE);
            // ポップアップメニュー
            hMenu       = LoadMenu( hInstance, MAKEINTRESOURCE(IDR_POPUPMENU) );
            hSubMenu1   = GetSubMenu( hMenu, 0 );  // IDR_MENU1
            hSubMenu2   = GetSubMenu( hMenu, 1 );  // IDR_MENU2
            // タスクトレイ
            nid.cbSize              = sizeof(nid);
            nid.hWnd                = hDlgWnd;
            nid.uID                 = IDI_ICON1;
            nid.uFlags              = NIF_MESSAGE | NIF_ICON | NIF_TIP;
            nid.uCallbackMessage    = WM_TASKTRAY;
            nid.hIcon               = LoadIcon( hInstance, MAKEINTRESOURCE(IDI_ICON1) );
            lstrcpy( nid.szTip, TEXT("tray") );
            break;
        }
        case WM_CLOSE:
            DestroyMenu( hMenu );
            EndDialog( hDlgWnd, wp );   // 返したい値をwpで受け取る
        case WM_COMMAND:
            switch ( LOWORD(wp) ){
                // タスクトレーに格納
                case ID_POPUPTASK:
                    Shell_NotifyIcon( NIM_ADD, &nid );
                    ShowWindow( hDlgWnd, SW_HIDE );
                    break;
                // 元に戻す
                case ID_RETURN:
                    ShowWindow( hDlgWnd, SW_SHOW );
                    Shell_NotifyIcon( NIM_DELETE, &nid );
                    break;
                case ID_POPUP1END:  PostMessage( hDlgWnd, WM_CLOSE, IDCANCEL, 0 );
                case ID_POPUP2END:  PostMessage( hDlgWnd, WM_CLOSE, 0, 0 );
                case IDCANCEL:      PostMessage( hDlgWnd, WM_CLOSE, 0, 0 );
                default:            return FALSE;
            }
            break;
        // 右クリック
        case WM_RBUTTONDOWN:
        {
            POINT pos;
            
            GetCursorPos( &pos );
            TrackPopupMenu( hSubMenu1, TPM_LEFTALIGN, pos.x, pos.y, 0, hDlgWnd, NULL );
        }
        // タスクトレイ時に
        case WM_TASKTRAY:
            switch ( lp ){
                case WM_RBUTTONDOWN:
                    GetCursorPos( &pos );
                    SetForegroundWindow( hDlgWnd );
                    TrackPopupMenu( hSubMenu2, TPM_RIGHTALIGN | TPM_BOTTOMALIGN, pos.x, pos.y, 0, hDlgWnd, NULL );
                    break;
            }
        default:return FALSE;
    }
    return TRUE;
}

// リソースは分けていたIDR_MENU1、IDR_MENU2を共通に書く
IDR_POPUPMENU MENU
BEGIN
    POPUP "IDR_MENU1のメニュー"
    BEGIN
        MENUITEM "タスクトレイに格納",    ID_POPUPTASK
        MENUITEM "終了",                  ID_POPUP1END
    END
    POPUP "IDR_MENU2のメニュー"
    BEGIN
        MENUITEM "タスクトレイから出す",  ID_RETURN
        MENUITEM "終了",                  ID_POPUP2END
    END
END

不要と思われる SetMenu、UpdateWindow、ClientToScreen、SetForegroundWindow を
取り除き WM_COMMAND メッセージをスッキリさせてみた。
これで動作の確認をしてみて下さい。


夏みかん  2008-07-31 13:33:13  No: 68807

追記。
WM_CLOSE と WM_RBUTTONDOWN の最後に break し忘れていた。
付けておいてね。


かもねぎ  2008-07-31 20:08:26  No: 68808

// 動くみたい
// tasktray.cpp
#include    <windows.h>
#include    <shellapi.h>
#include    "resource.h"

#define WM_TASKTRAY  ( WM_APP + 1 )

HINSTANCE   hInst ;
HMENU       hMenu, hSubMenu ;
NOTIFYICONDATA  nid ;

INT_PTR CALLBACK DialogProc ( HWND, UINT, WPARAM, LPARAM ) ;
BOOL On_Close ( HWND ) ;
BOOL On_Command ( HWND, WPARAM ) ;
BOOL id_Tasktray ( HWND ) ;
BOOL id_Return ( HWND ) ;
BOOL On_InitDialog ( HWND ) ;
BOOL On_RButtonUp ( HWND, LPARAM ) ;
BOOL On_Tasktray ( HWND, LPARAM ) ;

int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE, LPSTR, int )
{
    hInst = hInstance ;
    DialogBox ( hInst, MAKEINTRESOURCE ( IDD_DIALOG1 ), NULL, DialogProc ) ;
    return 0 ;
}

INT_PTR CALLBACK DialogProc ( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch ( msg )
    {
        case WM_INITDIALOG :    return On_InitDialog ( hDlg ) ;
        case WM_COMMAND :       return On_Command ( hDlg, wParam ) ;
        case WM_RBUTTONUP :     return On_RButtonUp ( hDlg, lParam ) ;
        case WM_CLOSE :         return On_Close ( hDlg ) ;
        case WM_TASKTRAY :      return On_Tasktray ( hDlg, lParam ) ;     
    }
    return FALSE ;
}

BOOL On_Tasktray ( HWND hDlg, LPARAM lParam )
{
    POINT pt ;
    
    if ( lParam == WM_RBUTTONUP )
    {
        GetCursorPos ( &pt ) ;
        SetForegroundWindow ( hDlg ) ;
      
        TrackPopupMenu ( hSubMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hDlg, NULL ) ;      
        return TRUE ;
    }

    return FALSE ;
}

BOOL On_InitDialog ( HWND hDlg )
{
    hMenu    = LoadMenu ( hInst, MAKEINTRESOURCE ( IDR_MENU1 ) ) ;
    hSubMenu = GetSubMenu ( hMenu, 0 ) ;
    EnableMenuItem ( hSubMenu, ID_RETURN, MF_GRAYED | MF_DISABLED ) ;

    nid.cbSize              = sizeof ( nid ) ;
    nid.hWnd                = hDlg ;
    nid.uID                 = IDI_ICON1 ;
    nid.uFlags              = NIF_MESSAGE | NIF_ICON | NIF_TIP ;
    nid.uCallbackMessage    = WM_TASKTRAY ;
    nid.hIcon               = LoadIcon ( hInst, MAKEINTRESOURCE ( IDI_ICON1 ) ) ;
    lstrcpy ( nid.szTip, TEXT ( "tray" ) ) ;

    return TRUE ;
}

BOOL On_RButtonUp (HWND hDlg, LPARAM lParam)
{
    POINT pt = { LOWORD ( lParam ), HIWORD ( lParam ) } ;

    ClientToScreen ( hDlg, &pt ) ;  
    TrackPopupMenu ( hSubMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hDlg, NULL ) ;

    return TRUE ;
}

BOOL On_Command ( HWND hDlg, WPARAM wParam )
{
    switch ( LOWORD ( wParam ) )
    {
        case ID_TASKTRAY :  return id_Tasktray ( hDlg ) ;
        case ID_RETURN :    return id_Return ( hDlg ) ;
        case IDCANCEL :     return On_Close ( hDlg ) ;
    }

    return FALSE ;
}

BOOL id_Tasktray ( HWND hDlg )
{
    Shell_NotifyIcon ( NIM_ADD, &nid ) ;
    ShowWindow ( hDlg, SW_HIDE ) ;

    EnableMenuItem ( hSubMenu, ID_RETURN, MF_ENABLED ) ;
    EnableMenuItem ( hSubMenu, ID_TASKTRAY, MF_GRAYED | MF_DISABLED ) ;

    return TRUE ;
}

BOOL id_Return ( HWND hDlg )
{
    Shell_NotifyIcon ( NIM_DELETE, &nid ) ;
    ShowWindow ( hDlg, SW_SHOW ) ;

    EnableMenuItem ( hSubMenu, ID_TASKTRAY, MF_ENABLED ) ;
    EnableMenuItem ( hSubMenu, ID_RETURN, MF_GRAYED | MF_DISABLED ) ;

    return TRUE ;
}

BOOL On_Close ( HWND hDlg )
{
    DestroyMenu ( hMenu ) ;
    EndDialog ( hDlg, 0 ) ;

    return TRUE ;
}

// resource.h

IDR_MENU1 MENU 
BEGIN
    POPUP "dummy"
    BEGIN
        MENUITEM "タスクトレイに移動(&T)",               ID_TASKTRAY
        MENUITEM "ダイアログボックスに移動(&D)",            ID_RETURN
    END
END


ttt  2008-08-01 20:07:19  No: 68809

仲澤@失業者さん>
{}を見直していましたら確かにあわさっていませんでした。
かもねぎさんの例のように関数化し、正しくしました。
夏みかんさん>
そのやりかたで上手くいきました。
サブメニューを二つ用意し、切り替えるということに囚われていて、
同じメニュー内で二つ用意できることを見逃していました。
閉じる系のメッセージもすっきりしてみやすくなりました。
ありがとうございます。
かもねぎ>
関数化した方が見ためもわかりやすさもアップしました。

みなさまありがとうございました。


ttt  2008-08-01 20:10:09  No: 68810

かもねぎさん>
関数化した方が見ためもわかりやすさもアップしました。

さんを付け忘れてしまいました。
失礼しました。


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

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






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