ViViではメニューのカスタマイズが可能になりましたが、この実装には大変苦労しました。 問題はいろいろあったのですが、そのひとつに『フレームウィンドウのメニューを変更する方法が解らない』 という問題がありました。今回はこれに関するお話です。Tipsというよりは解説記事ですね。
MFCではメニューを取り扱うためのクラス:CMenu が存在します。これは HMENU のラッパクラスで、
メニューそのものや、メニューアイテムを構築または動的に更新することが可能です。
もちろん参照もできるので、メニュー操作は思いのままです。
ViViのメニューカスタマイズでは、メニュー構造の情報を保持するクラス;メニューマネージャを用意し、
これを仲介にして、レジストリまたはテキストファイルへの情報の保存とリストア、
メニューオブジェクトの構築を行っています。
┏━━━━━━━━━┓ ┃デフォルトメニュー┠────┐ ┗━━━━━━━━━┛ │ ┏━━━━━━━━━┓ ┏━━━━━━━━━┓ └──→┃ ┃ ┌────────┐ ┃ レジストリ ┃←──────→┃メニューマネージャ┃←───│カスタマイズ処理│ ┗━━━━━━━━━┛ ┌──→┃ ┃ └────────┘ ┏━━━━━━━━━┓ │ ┗━━━━━━━━━┛ ┃テキストファイル ┃←───┘ │ ┗━━━━━━━━━┛ │ ↓ ┌──────────┐ │メニューオブジェクト│ └──────────┘ |
そういうわけで、メニュマネージャクラスはメニューオブジェクトをカスタマイズし、 結果を CMenu または HMENU に反映させることができるようになりました。 しかし、ここでハタと困ってしまいました。 『フレームウィンドウのメニューを変更するにはどうしたらいいんでしょう?』
Windows のウィンドウはメニューを保持することができます。MFCでは CWnd::SetMenu(CMenu* pMenu) でメニューを変更できます。当初は、新しく作成したメニューを指定すれば楽勝だな、と思っていましたが、 実際にこのAPIをコールすると、確かにメニューは変更されます。「ふっふっふ、オイラもMFCエキスパートに 近づいてきたかな」と喜びつつ画面をよく見ると、メニューの左にあるViViアイコンが表示されません。 さらによく見るとメニューの列にある3つのアイコンも表示されていません。(T_T)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃V _□×┃ ┠────────────────────────────────────────┨ ┃V ファイル 編集 検索 表示 ツール ウィンドウ ヘルプ _□×┃ ┠────────────────────────────────────────┨ ↑ ↑ これが無い これらも無い |
賢明なデベロッパの人はお気づきでしょう。アイコンたちはメインフレームのメニューアイテムだったんですね。 「う〜ん。さすがMFC、こんな実装方法を採っていたとは」と関心しつつ、オンラインヘルプを繰り出す筆者でした。
そこで発見したのが CMDIFrameWnd::MDISetMenu です。このメソッドを使うとメニューを変更し、
さらにアイコンたちをそのままにしてくれます。修正版をHPにアップし、「ふっふっふ、こんないいメソッドがあるとは知らなかったぜ。
またひとつ野望に近づいたな」と自己満足していたら、ViViの試用版のユーザ様(福岡のMさんありがとうございました)から、
新しくドキュメントを開くとメニューの設定がデフォルトに戻る、という指摘がありました。
賢明なデベロッパの人はもうお気づきでしょう。MDISetMenu は現在のドキュメントのメニューを変更するだけで、
あらたに作成されるドキュメントのメニューまでは変更してくれないのです。(T_T)
┏━━━━━━━┓ ┏━━━━━━━┓ ┏━━━━━━━┓ ┃ドキュメント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
までメールをいただければ幸いです。