none
CMenuのGetSubMenuで得たCMenuのポインタを、関数に引数渡しすると、渡した先でセパレートが判断できない RRS feed

  • 質問

  • いつもお世話になります。

    質問の内容は、件名の通りです。
    ポップアップメニューを実装しようと思い、下記のようにコーディングしましたが、下請け関数内でGetMenuItemID関数でアイテムがセパレートかどうかを判断することができません。
    ただし、VS2005ではNGですが、VS2008では正常に動作しているように見えます。
    GetMenuStateやGetMenuItemIDなどで外側で判断するだけでなく、CMenu内部にきちんとセパレートを保持したままにしたいのです。
    関数に渡しただけで各アイテムの内容が変わるようにしたくないのですが、良い方法はないでしょうか。
    下請け関数にCMenuを渡す仕組みはそのままにしたいです。
    よろしくお願いいたします。

    1BOOL TrackPopupMenuEx( const CMenu* pSubMenu, UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect) 
    2
      // 確認用 
    3    for( UINT nItem = 0; nItem < pSubMenu->GetMenuItemCount(); nItem++) 
    4    { 
        BOOL IsSeparate = FALSE; 

    5        int nState = pSubMenu->GetMenuState( nItem, MF_BYPOSITION); 
    6        if( nState& MF_SEPARATOR) 
    7            IsSeparate = TRUE; // OK 
    8 
    9        int nID = pSubMenu->GetMenuItemID( nItem);                                              
    10        if( nID == ID_SEPARATOR) 
    11            IsSeparate = TRUE; // NG 
    12 
    13        CString rString; 
    14        int rStringMax = pSubMenu->GetMenuString( nItem, rString, MF_BYPOSITION); 

    15    }
      pSubMenu->TrackPopupMenu( nFlags, x, y, pWnd, lpRect);
    16    return TRUE; 
    17
    18 
    19void CxxxxView::OnRButtonUp(UINT nFlags, CPoint point) 
    20{ 
    21    CMenu Menu; 
    22    Menu.LoadMenu( IDR_MENU1); 
    23    CMenu* pSubMenu = Menu.GetSubMenu(0);   
    24    ClientToScreen( &point); 
    25 
    26    // セパレータを末尾に加える 
    27    pSubMenu->AppendMenu( MF_SEPARATOR, NULL, ""); 
    28 
    29    // 確認用 
    30    for( UINT nItem = 0; nItem < pSubMenu->GetMenuItemCount(); nItem++) 
    31    { 
    32        BOOL IsSeparate = FALSE; 
    33 
    34        int nState = pSubMenu->GetMenuState( nItem, MF_BYPOSITION); 
    35        if( nState& MF_SEPARATOR) 
    36            IsSeparate = TRUE; // OK 
    37 
    38        int nID = pSubMenu->GetMenuItemID( nItem);           
    39        if( nID == ID_SEPARATOR) 
    40            IsSeparate = TRUE; // OK 
    41 
    42        CString rString; 
    43        int rStringMax = pSubMenu->GetMenuString( nItem, rString, MF_BYPOSITION); 
    44    } 
    45 
    46    TrackPopupMenuEx( pSubMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this, NULL); 
    47} 




    • 編集済み maechang 2009年2月14日 5:46 TrackPopupMenuがfor文の中にあったので、外に出した。
    2009年2月13日 12:13

すべての返信

  • 投稿する際の写し間違いなのかもしれませんが、TrackPopupMenuExの方はpSubMenuがないのですが、pMenuの打ち間違いでしょうか?

    なお、VS2005の環境が手元に残っていないので、検証とかはできていません。orz
    参考になった返信には「回答としてマーク」のボタンを利用して、回答に設定しましょう(未解決の場合を除く)。
    2009年2月13日 14:44
    モデレータ
  • Azuleanさん、いつもお世話になります。

    ご指摘の通りですね><
    ソースコードは手元にあるものをページに貼り付けて、ごにょごにょしてしまいました。orz
    修正します。ありがとうございます。

    VS2008で正常動作しているように見えるのは、もしかするとVS2005でも正常動作することがあるのかもしれませんが、
    環境の問題とか絡んでいるかもしれません。(多分違うと思っていますが
    色々ググったのですが、それらしい内容が見つからず・・・。
    この件で半日潰れました(泣
    2009年2月13日 17:51
  • 前田悟 の発言:

    GetMenuStateやGetMenuItemIDなどで外側で判断するだけでなく、CMenu内部にきちんとセパレートを保持したままにしたいのです。
    関数に渡しただけで各アイテムの内容が変わるようにしたくないのですが、良い方法はないでしょうか。


    そういわれても、CMenuは「WindowsのHMENUをカプセル化したものです。」ですし、メンバ変数も「HMENU m_hMenu」しかありません。
    GetMenuState()で調べるのが正解だと思います。
    GetMenuItemID()の方はわかりません。同名のWindows APIを呼び出しているだけなので、OS依存で変な値が返ってきているのでしょうか?
    2009年2月14日 0:37
  • 佐祐理さん、ご回答ありがとうございます。

    仰られるとおり、GetMenuStateでセパレートが判断できているので、GetMenuItemIDの疑問は残りますが、
    判断はできるので、これでいこうと思いました。

    実は、今回の投稿に至った経緯は、CMenuのポインタを下請け関数に渡してから表示すると、セパレートが別アイテムに置き換わるという現象が発生したためでした。
    それで、下請け関数内でfor文を使って内部のアイテムを確認したところ、GetMenuItemIDのIDがセパレートのところだけ、別のアイテムになっており、NG。
    (セパレート部分はポップアップメニューに表示する一番上部のアイテムになっていました)

    そして、下請け関数に渡す前は正常なのか?を調べるために、GetSubMenuをした後に内部のアイテムを確認するとOK。

    更に、全く同じコードをVS2008で実行すると、GetMenuItemIDは正常で、ポップアップメニューも正しく表示されます。

    すみません。件名が正しくありませんでした。投稿してから間違いに気づきました。
    セパレートを判断したいのは、デバッグコードの都合であって、質問したかった内容は、「なぜ下請け関数ではポップアップメニューのセパレートが正しくないのか」でした。

    根本原因が分かればとても嬉しいのですが、もし「下請け関数内でポップアップメニューのセパレートを正しく表示する方法」があれば
    是非ご教授下さい。
    2009年2月14日 5:43
  • ですから、CMenu::GetMenuItemID()は
    _AFXWIN_INLINE UINT CMenu::GetMenuItemID(int nPos) const 
      { ASSERT(::IsMenu(m_hMenu)); return ::GetMenuItemID(m_hMenu, nPos); } 
    という定義で、WindowsのAPIを呼び出すだけですのでMFCのバージョン(VS2005かVS2008か)に依りません。
    プログラムがバグっていてどこかでメニューを書き換えているか、環境が壊れているか、ぐらいしか思い浮かびません。
    ::GetMenuItemID()にバグがあったのならば、VS2008でも同様の現象が発生するかと。

    ところで影響ないかもしれませんが、pSubMenu->AppendMenu( MF_SEPARATOR );(引数を2つ減らす)と状況は変わりますか?
    一応、ドキュメントには値は捨てられるとなっていますが…。
    あとは::GetMenuItemCount()はエラー時に-1を返します。CMenu::GetMenuItemCount()はそれを無理矢理キャストしてUINT_MAXを返すのでループが余分に回ってしまいます。
    毎ループpSubMenu->GetMenuItemCount()を呼び出すことを止めると変わりますか?
    2009年2月14日 7:49
  • 佐祐理さん、ご回答ありがとうございます。

    ご指摘通り、Win32APIを利用しているので、環境が壊れている可能性も否定できませんね。
    元々、大規模なシステムの中で実装したときにおかしかったので、誰かが悪さしているのかな?と思いました。
    CFrameWndでオーナードローやその他ごにょごにょしている関係で、その影響が出ているのかもしれないとも思いました。

    で、ポップアップメニュー部分だけを抜いて、新規SDIアプリを作って、そこに今回のソース(ファイルではなくて)を持っていって検証したところ、
    やはり同様の問題が発生し、使い方が間違っているか、APIのバグか?と考えました。
    APIのバグの可能性は低いと思い、多分使い方の問題だと思いました。

    VS2005の環境は会社にしかないので、月曜日に佐祐理さんご指摘部分を検証してみたいと思います。
    ありがとうございます。


    pSubMenu->AppendMenu( MF_SEPARATOR);


    int  max = pSubMenu->GetMenuItemCount();
    for( UINT nItem = 0; nItem < max ; nItem++)
    2009年2月14日 9:40
  •  VS2005SP1の環境で試したところ、再現しませんでした。

    MFCのSDIアプリケーションを新規に作成し、メニューアイテムを1個だけ持つIDR_MENU1を追加し、C***Viewにコードを追加しています。
    確認方法として、IsSeparate = TRUE;ではなく、AfxMessageBoxに置き換えています。
    (Unicode/MBCS共に確認済み)


    ところで、NGというのはどうやって確認したんでしょうか?
    ブレークを張ったりステップ実行だったりしたのでしょうか?
    参考になった返信には「回答としてマーク」のボタンを利用して、回答に設定しましょう(未解決の場合を除く)。
    2009年2月14日 10:49
    モデレータ
  • Azuleanさん、ご回答ありがとうございます。
    VS2005で検証していただいて、本当にありがとうございます。

    Azuleanさんの環境では再現しませんでしたか・・・。
    書き忘れていましたが、検証したマシンの環境は、WindowsXPProSP3、VS2005SP1です。
    また、MBCSで確認しました。

    NGの確認方法ですが、ブレークポイントでステップ実行してNGだったのと、
    ステップ実行ではなく、そのまま実行してもNGでした。
    (どちらもデバッグ情報付きです)











    2009年2月14日 15:03
  • このケースで関数呼び出し云々がどう影響したかなどはわかりませんが、
    以前に セパレータのメニューIDをGetMenuItemID()を用いて取得した際に、0xfffeを返すことがあることを確認したことがあります。
    Windows XPではそのような現象が確認できましたが、Windows2000では確認することができませんでした。

    セパレータかどうかはGetMenuItemInfo()でMENUITEMINFOのftypeがMFT_SEPARATORを含むかどうかで確認するのがよいと思います。
    2009年2月15日 6:16
  • Atsushi777さん、ご回答ありがとうございます。
    また、貴重な情報ありがとうございます。

    GetMenuItemInfoでセパレータが正しく取れるか確認したいと思います。

    すみません。質問のタイトルを間違えてしまいまして・・・。
    上の方にも書きましたが、セパレートを外で判断するのは、デバッグするための都合で、
    どうも内部的にはセパレート扱いになっていないため、ポップアップメニューを表示すると、セパレータ部分が別アイテム要素に置き換わってしまいます。

    もし何か情報があれば是非教えて頂きたく。
    明日、GetMenuItemInfoを確認してみたいと思います。
    ありがとうございます。
    2009年2月15日 9:03
  • Atsushi777さん、回答が遅くなり申し訳ありません。

    下記のコードでセパレーターを判断することが出来ました。
    ありがとうございます。

    for( UINT nItem = 0; nItem < pMenu->GetMenuItemCount(); nItem++)
    {
    MENUITEMINFO info = {0,};
    info.cbSize = sizeof( info);
    info.fMask = MIIM_TYPE;
    pMenu->GetMenuItemInfo( nItem, &info, TRUE);
    if( info.fType & MFT_SEPARATOR)
    IsSeparate = TRUE; // OK

    }

    2009年2月17日 3:59
  • Azuleanさん、佐祐理さん、ご返答が遅くなり申し訳ありません。

    実は、投稿した問題が再現しなくなり、困っています。
    問題の発生したマシンで確認しているのですが、再現しましたらご指摘頂いた部分を調査してみます。

    一旦保留にさせてください。すみません。

    2009年2月17日 10:56