2つほど、質問させていただきます。
1つは、CMenu派生クラスを使用してオーナードロウメニューを作成しました。
(もちろん、DrawItem、MeasureItemをオーバーライドしています)
このクラスを使用して、AppendMenu()で「MF_OWNERDAW」を指定して、
TrackPopupMenu()で右クリックでポップアップメニューを表示させました。
しかし、サブメニューを持つアイテムのみ、表示がおかしいのです。
デバッグしてみたところ、サブメニューを持つアイテムのみ、
MeasureItem()が呼び出されていませんでした。
かわりに、「warning: unknown WM_MEASUREITEM for menu item 〜」
なるメッセージがでています。
どうも、アイテムのIDが認識できませんということみたいですが、
DrawItem()は、ちゃんと呼び出されて、描画されています。
どうすれば、サブメニューを持つアイテムもMeasureItem()が
呼び出されるのでしょうか?
一応、メニューを持っているウィンドウでWM_MEASUREITEMメッセージを
受信したら、MeasureItem()を呼び出すことで対応しています。
もう1つの質問は、質問のオーナードロウを行うと、Windows98では
正常に表示されるのですが、XP上で表示すると枠だけ表示され、
選択状態になったアイテムだけ描画されていきます。
また、この後、他のアプリのメニューも同じ現象になってしまいます。
解決策を教えていただけないでしょうか?
以上、どなたか解凍をお願いいたします。
誰もレスしないんで、
ダイアログアプリで追跡してみようかと思ったけど、
どうもあなたの方法/現象をトレースできない。
MDI/SDI のフレームウィンドウのメニューなのか、ポップアップメニューなのか。
あなたのコードを示してください。
CMenu 派生クラスは Class Wizard で自動的に作ってくれないのでその作成手順も。
あと開発環境もね。(当方 P41.9G/Win2ksp4/VC++6.0sp5)
>> DrawItem()は、ちゃんと呼び出されて、描画されています
オーナ描画なら、OnDrawItem() ではないの?
http://www.microsoft.com/japan/developer/library/vcmfc/_mfcnotes_tn014.htm
>> 以上、どなたか解凍をお願いいたします
ヒノカグツチが必要。(^^;;
レスありがとうございます。
メニューはダイアログ上のOnRButtonUp()ないで、TrackPopupMenu()で
ポップアップメニューとして表示させています。
開発環境は、celeron700MHz/Windows98SE/VC++ sp5 です。
メニュークラスはClass Wizardで「MFC:generic CWnd」で
「CCustomMenu」を作成して、CWndからの継承をCMenuに書き換えました。
(宣言:class CCustomMenu : public CMenu{ 〜 };)
そして、
virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
virtual void MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct );
として、オーバーライドしています。
また、アイテム追加用関数を作成しました。CMenu::AppnedMenuとほとんど同じ仕様です。
(オーナードロウの場合、アイテム情報を自前で用意しなければならないようなので)
m_listItemPtrはCList型で、自前のアイテム情報構造体のポインタのリストです。
BOOL CCustomMenu::AppendItem
(UINT uFlags, UINT uID, LPCTSTR strText, HICON hIcon)
{
//セパレータなら、アイテム情報は作成しません
if( uFlags & MF_SEPARATOR )
return CMenu::AppendMenu( MF_OWNERDRAW);
//アイテム情報の設定
PCUSTOMMENUITEM pitem = new CUSTOMMENUITEM;
m_listItemPtr.AddTail(pitem);
pitem->uID = uID;
pitem->strText = strText;
pitem->hIcon = hIcon;
pitem->pSubMenu = NULL;
//サブメニューの場合、新たにサブメニューを作成します
if( uFlags & MF_POPUP )
{
CCustomMenu* pSubMenu = (CCustomMenu*)uID;
pitem->pSubMenu = new CCustomMenu;
pitem->pSubMenu->Attach(pSubMenu->Detach());
uID = (UINT)(pitem->pSubMenu->GetSafeHmenu());
}
return CMenu::AppendMenu(uFlags | MF_OWNERDRAW, uID, (LPCTSTR)pitem);
}
これで、ポップアップメニューを以下のように表示させます。
void CTestCustomMenuDlg::OnRButtonUp(UINT nFlags, CPoint point)
{
CPoint pt = point;
ClientToScreen(&pt);
CCustomMenu sub2;
sub2.CreatePopupMenu();
sub2.AppendItem( NULL, 31, "menu3-1");
CCustomMenu sub1;
sub1.CreatePopupMenu();
sub1.AppendItem( NULL, 21, "menu2-1");
sub1.AppendItem( MF_POPUP, (UINT)&sub2, "menu2-2");
sub1.AppendItem( NULL, 23, "menu2-3");
CCustomMenu menu;
menu.CreatePopupMenu();
menu.AppendItem( NULL, 11, "menu1-1");
menu.AppendItem( MF_POPUP, (UINT)&sub1, "menu1-2");
menu.AppendItem( NULL, 13, "menu1-3");
menu.TrackPopupMenu( TPM_LEFTALIGN, pt.x, pt.y, this);
CDialog::OnRButtonUp(nFlags, point);
}
すると、MF_POPUPを指定しているアイテムだけオーバーライドした
CCustomMenu::MeasureItem()を呼び出してくれません。
(DrawItemはやっぱり呼び出されています。)
また、この代行策は、メニューアイテム情報を持つための親クラスを持たない
クラスを作成して、仮想関数でないDrawItem/MeaureItemを用意しました。
そして、メニュー呼び出したダイアログウィンドウのWindowProc()を
オーバーライドして、以下のようにしてオーナードロウを実現しました。
if( message == WM_DRAWITEM && wParam == 0 )
{
DrawItem( (LPDRAWITEMSTRUCT)lParam);
}
else if( message == WM_MEASUREITEM && wParam == 0 )
{
MeasureItem( (LPMEASUREITEMSTRUCT)lParam);
}
ただ、どちらの方法でもWindows98では通常に表示されますが
WinodwsXPでは最初の発言のような現象になります。
まずは確認で。
CustomMenu.cpp:
BEGIN_MESSAGE_MAP(CCustomMenu, CWnd)
//{{AFX_MSG_MAP(CCustomMenu)
// メモ - ClassWizard はこの位置にマッピング用のマクロを追加または削除します。
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
このままでは CWnd のプロテクトメンバに引っかかるので
とりあえず、CWndをCCustomMenuとして通す。
CCustomMenu:Constructor();
CCustomMenu:Constructor();
CCustomMenu:Constructor();
CCustomMenu:Constructor();
CCustomMenu:Constructor();
CCustomMenu::MeasureItem(itemID=0000000B):
Warning: unknown WM_MEASUREITEM for menu item 0x4E80D03.
CCustomMenu::MeasureItem(itemID=0000000D):
CCustomMenu::DrawItem(itemID=0000000B):
CCustomMenu::DrawItem(itemID=04E80D03):
CCustomMenu::DrawItem(itemID=0000000D):
CCustomMenu:Destructor();
CCustomMenu:Destructor();
CCustomMenu:Destructor();
CCustomMenu:Destructor();
CCustomMenu:Destructor();
確かにそう出ますね。
でも、コンストラクタ、デストラクタが必要以上に呼ばれているねぇ。
void CCustomMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
TRACE("CCustomMenu::MeasureItem(itemID=%08X):\n",lpMeasureItemStruct->itemID);
}
TRACE() にブレークポイントを張り、これを呼ぶ元を探ります。
// Measure item implementation relies on unique control/menu IDs
void CWnd::OnMeasureItem(int /*nIDCtl*/, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (lpMeasureItemStruct->CtlType == ODT_MENU)
{
ASSERT(lpMeasureItemStruct->CtlID == 0);
CMenu* pMenu;
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
if (pThreadState->m_hTrackingWindow == m_hWnd)
{
// start from popup
pMenu = CMenu::FromHandle(pThreadState->m_hTrackingMenu);
}
else
{
// start from menubar
pMenu = GetMenu();
}
pMenu = _AfxFindPopupMenuFromID(pMenu, lpMeasureItemStruct->itemID);
if (pMenu != NULL)
pMenu->MeasureItem(lpMeasureItemStruct);
else
TRACE1("Warning: unknown WM_MEASUREITEM for menu item 0x%04X.\n",
lpMeasureItemStruct->itemID);
>>>>....なるメッセージがでています。
これだね!
}
else
{
CWnd* pChild = GetDescendantWindow(lpMeasureItemStruct->CtlID, TRUE);
if (pChild != NULL && pChild->SendChildNotifyLastMsg())
return; // eaten by child
}
// not handled - do default
Default();
}
_AfxFindPopupMenuFromID(pMenu, lpMeasureItemStruct->itemID);
が NULL を返しているのを突き止めたので、
ここにブレークポイントを張り、中に潜ります。
AFX_STATIC CMenu* AFXAPI _AfxFindPopupMenuFromID(CMenu* pMenu, UINT nID)
{
ASSERT_VALID(pMenu);
// walk through all items, looking for ID match
UINT nItems = pMenu->GetMenuItemCount();
for (int iItem = 0; iItem < (int)nItems; iItem++)
{
CMenu* pPopup = pMenu->GetSubMenu(iItem);
if (pPopup != NULL)
{
// recurse to child popup
pPopup = _AfxFindPopupMenuFromID(pPopup, nID);
// check popups on this popup
if (pPopup != NULL)
return pPopup;
}
else if (pMenu->GetMenuItemID(iItem) == nID)
{
// it is a normal item inside our popup
pMenu = CMenu::FromHandlePermanent(pMenu->m_hMenu);
return pMenu;
}
}
// not found
return NULL;
}
コールスタック:
_AfxFindPopupMenuFromID(CMenu * 0x00344f98 {CCustomMenu}, unsigned int 7736337) line 1236
_AfxFindPopupMenuFromID(CMenu * 0x003451c0 {CCustomMenu}, unsigned int 7736337) line 1240 + 13 bytes
_AfxFindPopupMenuFromID(CMenu * 0x0012f944 {CCustomMenu}, unsigned int 7736337) line 1240 + 13 bytes
CWnd::OnMeasureItem(int 0, tagMEASUREITEMSTRUCT * 0x0012f690) line 1276 + 16 bytes
CWnd::OnWndMsg(unsigned int 44, unsigned int 0, long 1242768, long * 0x0012f53c) line 1930
CWnd::WindowProc(unsigned int 44, unsigned int 0, long 1242768) line 1585 + 30 bytes
AfxCallWndProc(CWnd * 0x0012fe74 {CDm02Dlg hWnd=0x00720156}, HWND__ * 0x00720156, unsigned int 44, unsigned int 0, long 1242768) line 215 + 26 bytes
AfxWndProc(HWND__ * 0x00720156, unsigned int 44, unsigned int 0, long 1242768) line 368
どうやら、サブメニューが存在していると
たとえ、現在の pMenu が目的のアイテムを示していても、
再帰してしまい、見つからないということになるようです。
Win98 だとここのロジックが違うのかな。
前述のように当方 Win2K なのですが。
どうなってます?
コールスタックを見る以上、
デフォルトで OnMeasureItem() -> MeasureItem() と呼ばれているので、
メッセージハンドラ:OnMeasureItem(), OnDrawItem() を追加し、
そこに処理を記述するのが筋だと思います。
どうでしょうか。
このようにすれば、DrawItem(), MeasureItem() を手書きで
追加する必要もなかったかと思うのですが。
たしかに、OnMeausreItem/OnDrawItemメッセージハンドラでうまくいきます。
Win98でもコールスタックは同じような結果になります。
どうやら、CMenuの仮想関数 MeasureItem/DrawItemで処理するよりも
メニューのオーナーであるウィンドウでメッセージを捕らえたほうが
確実なようですね。
また、WinodwsXPでメニューの表示がおかしくなる現象ですが、
WinXPでは、初回のWM_DRAWITEMメッセージでは
DRAWITEMSTRUCT構造体のデバイスコンテキストでペン・ブラシなどを
選択するSelectObject()でエラーが起きていました。
このSelectObject()の戻り値(NULLになっていました)を、
DrawItemの終わりにデバイスコンテキストに戻していたので
ほかのメニューも表示がおかしくなっていたようです。
なぜ、Win98では初回のWM_DRAWITEMメッセージでSelectObject()が成功するのに、
WinXPでは失敗するのでしょう?
>> WinodwsXPでメニューの表示がおかしくなる現象
パフォーマンスという意味ではいいかげんなコードですが、(^^;;
(CDC/CBitmap 関連は ダイアログのメンバにしておくべきだよね)
描画については、Win 2000 と Win XP Pro の両方で
ちゃんと動いてますよ。単なる BitBlt だけですが。
IDB_BITMAP1 は 48x48 のビットマップ。
これを16x16単位で切り出して使用しています。
void CCustomMenu::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
int cx = 0,cy = 0;
switch( lpDrawItemStruct->itemID )
{
case 0x0B:
cx = 16;
break;
case 0x0D:
cy = 16;
break;
case 0x1F:
cx = cy = 16;
break;
case 0x17:
cx = 16; cy = 32;
break;
}
HDC hDC = lpDrawItemStruct->hDC;
CDC cdc;
CDC cdcMem;
cdc.Attach( hDC );
CBitmap bmp,*pbmp;
cdcMem.CreateCompatibleDC( &cdc );
bmp.LoadBitmap( IDB_BITMAP1 );
pbmp = cdcMem.SelectObject( &bmp );
cdc.BitBlt( lpDrawItemStruct->rcItem.left,lpDrawItemStruct->rcItem.top,16,16,&cdcMem, cx,cy,SRCCOPY );
cdcMem.SelectObject( pbmp );
cdcMem.DeleteDC();
bmp.DeleteObject();
cdc.Detach();
TRACE("CCustomMenu::DrawItem(itemID=%08X):\n",lpDrawItemStruct->itemID);
}
↑この例、やはりポップアップのところだけ
MeasureItem がこないため、高さを32 にすると
うまくいきません(画像が重なります)。
やはり OnXXXX が筋ですかね。
MFC内部の方法に倣って、
OnDrawItem() から DrawItem() を呼び出せばよいかも。
話は変わりますけど、
実は私も今はまってまして、
構造化プログラミングという意味では、
メニューの構造とプログラムの構造は分けるべきだとおもい、
OnRButtonDown でメニューにサブメニューを差し込んで、
メニュー構造を構築するのではなく、
IDR_MENU なるリソースからツリーその構造を取り出して
CCustomMenu 側で描画しようと考えています。
ポイントはメニューリソースの構造を読み込むところなんですよ。
読み込んで、普通の(デフォルトの)動作はするんですが、
DrawItem が呼ばれないので、オーナドローにもセルフドローにも
ど.うにもならない。(^^;;
〃
<ろ>
XPで表示がおかしくなる現象については、デバイスコンテキストに
ペンやブラシを選択した時に失敗した場合は(SelectObject()の戻り値がNULL)、
これを元に戻さないようにすることで、回避できました。
Win98では、WM_DRAWITEMメッセージデバイスコンテキストの
SelectObject()が失敗することがないのに、
WinXPでは、なぜか、失敗することがあるようです。
「なーめ」さん、丁寧なレス、ありがとうございました。