Tips10 フレームウィンドウのメニュー

前のTips 次のTips


 ViViではメニューのカスタマイズが可能になりましたが、この実装には大変苦労しました。 問題はいろいろあったのですが、そのひとつに『フレームウィンドウのメニューを変更する方法が解らない』 という問題がありました。今回はこれに関するお話です。Tipsというよりは解説記事ですね。

■ MFCでのメニュー

 MFCではメニューを取り扱うためのクラス:CMenu が存在します。これは HMENU のラッパクラスで、 メニューそのものや、メニューアイテムを構築または動的に更新することが可能です。 もちろん参照もできるので、メニュー操作は思いのままです。
 ViViのメニューカスタマイズでは、メニュー構造の情報を保持するクラス;メニューマネージャを用意し、 これを仲介にして、レジストリまたはテキストファイルへの情報の保存とリストア、 メニューオブジェクトの構築を行っています。

┏━━━━━━━━━┓
┃デフォルトメニュー┠────┐
┗━━━━━━━━━┛    │   ┏━━━━━━━━━┓
┏━━━━━━━━━┓    └──→┃         ┃    ┌────────┐
┃  レジストリ  ┃←──────→┃メニューマネージャ┃←───│カスタマイズ処理│
┗━━━━━━━━━┛    ┌──→┃         ┃    └────────┘
┏━━━━━━━━━┓    │   ┗━━━━━━━━━┛
┃テキストファイル ┃←───┘        │
┗━━━━━━━━━┛             │
                        ↓
                   ┌──────────┐
                   │メニューオブジェクト│
                   └──────────┘

 そういうわけで、メニュマネージャクラスはメニューオブジェクトをカスタマイズし、 結果を CMenu または HMENU に反映させることができるようになりました。 しかし、ここでハタと困ってしまいました。 『フレームウィンドウのメニューを変更するにはどうしたらいいんでしょう?』

■ MFCでのメニューの変更

 Windows のウィンドウはメニューを保持することができます。MFCでは CWnd::SetMenu(CMenu* pMenu) でメニューを変更できます。当初は、新しく作成したメニューを指定すれば楽勝だな、と思っていましたが、 実際にこのAPIをコールすると、確かにメニューは変更されます。「ふっふっふ、オイラもMFCエキスパートに 近づいてきたかな」と喜びつつ画面をよく見ると、メニューの左にあるViViアイコンが表示されません。 さらによく見るとメニューの列にある3つのアイコンも表示されていません。(T_T)

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃V                                    _□×┃
┠────────────────────────────────────────┨
┃V ファイル 編集 検索 表示 ツール ウィンドウ ヘルプ        _□×┃
┠────────────────────────────────────────┨
 ↑                                     ↑
 これが無い                               これらも無い

 賢明なデベロッパの人はお気づきでしょう。アイコンたちはメインフレームのメニューアイテムだったんですね。 「う〜ん。さすがMFC、こんな実装方法を採っていたとは」と関心しつつ、オンラインヘルプを繰り出す筆者でした。

 そこで発見したのが CMDIFrameWnd::MDISetMenu です。このメソッドを使うとメニューを変更し、 さらにアイコンたちをそのままにしてくれます。修正版をHPにアップし、「ふっふっふ、こんないいメソッドがあるとは知らなかったぜ。 またひとつ野望に近づいたな」と自己満足していたら、ViViの試用版のユーザ様(福岡のMさんありがとうございました)から、 新しくドキュメントを開くとメニューの設定がデフォルトに戻る、という指摘がありました。
 賢明なデベロッパの人はもうお気づきでしょう。MDISetMenu は現在のドキュメントのメニューを変更するだけで、 あらたに作成されるドキュメントのメニューまでは変更してくれないのです。(T_T)

 MFCのソースをよ〜く読むと、メニューはドキュメントテンプレートクラスのオブジェクトが管理していて、 ドキュメントが選択されると、それに対応するメニューが有効になるような仕組みになっていることが解りました。

 ┏━━━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━┓
 ┃ドキュメント1┃ ┃ドキュメント2┃ ┃ドキュメント3┃  ドキュメントテンプレート
 ┗━━━━━━━┛ ┗━━━━━━━┛ ┗━━━━━━━┛    m_hMenuShared
     │         │         │          │
     └────────┐│┌────────┘          │
              ↓↓↓メニューハンドル           │
          ┏━━━━━━━━━┓               │
          ┃フレームウィンドウ┃←──────────────┘
          ┗━━━━━━━━━┛  ドキュメントが固有のメニューを持たない時

 という訳で、ドキュメントテンプレートが m_hMenuShared に保持する共有メニュー、 これを変更するのが正しい実装方法だったんですね。

 こんどこそ完璧、と思ってビルドを行い、プログラムを走らせたところ、 今度はアサーションが発生してしまいました。CMDIChildWnd が有効なオブジェクトではないらしんです。 その原因は、CMDIChildWnd が保持しているメニューハンドルが不正だからで、そのハンドルは古いハンドルで、 MDISetMenu で再設定したときにハンドルをデストロイしているからでした。このあたりをちゃんと理解するには、 フレームウィンドウまわりのクラスをちゃんと理解する必要があります。理解していない人は、 ヘルプをよく読みましょう。
 結局、メニューを置き換えるプログラムは以下のようにしました。

    .....
    CMenu nMenu;
    nMenu.CreateMenu();
    VERIFY(m_menuMgr.toMenu(MENUIX_MAIN, &nMenu));          //  設定されたメニューからメニューを構築する
    HMENU hMenu = nMenu.Detach();                           //  メニューハンドルを取り出す
    HMENU hOldMenu = m_multiDocTemplate->m_hMenuShared;     //  現状のメインメニュー
    m_multiDocTemplate->m_hMenuShared = hMenu;              //  メニューを置き換える
    ((CMainFrame *)m_pMainWnd)->changeFrameMenu(hMenu);     //  フレームウィンドウでの処理
    DestroyMenu(hOldMenu);                                  //  古いメニューよさようなら
    .....

    void CMainFrame::changeFrameMenu(HMENU hMenu)
    {
        OnUpdateFrameMenu(hMenu);                           //  メニューを変更する
    
        CWnd clWnd; 
        clWnd.Attach(m_hWndMDIClient);
        CWnd *child = clWnd.GetWindow(GW_CHILD);
        while( child != NULL ) {
            ((CChildFrame *)child)->setSharedMenu(hMenu);   //  子ウィンドウのメニューを変更
            child = child->GetNextWindow();
        }
        clWnd.Detach();
        DrawMenuBar();                                      //  メニューを再表示
    }

    class CChildFrame : public CMDIChildWnd
    {
        .....
    public:
        void    setSharedMenu(HMENU hMenu)      { m_hMenuShared = hMenu; }; 
        .....
    };

 これはいまのところ(ビルド#0077以降)問題なく動作しているようです。


前のTips 次のTips 津田伸秀 のホームページに戻る。

Last Updated on 6-Oct-1996, Copyright (c) 1996 by Nobuhide Tsuda, All Right Reserved.
このホームページに関するご質問、ご要望、バグレポート等は  Nobuhide_Tsuda@jsn.justnet.or.jp  までメールをいただければ幸いです。