CLuxMenuクラス
CLuxMenuクラスでは、既存メニューのインポートから描画まで、メニューの編集機能を提供するクラスモジュールがコーディングされています。すべてを解説すると量が多くなってしまうので、今回は以下のポイントを重視して説明します(その他の情報に関しては、ソースコードを参照)。
メンバとコンストラクタ、デストラクタ
まずは、このクラスで必要になるメンバとコンストラクタ、デストラクタを定義していきます。
Class CLuxMenu Inherits CLuxCtrlBase 'COM参照カウンタ m_ref As Long Protected m_hMenu As HMENU m_hMenuFont As HFONT MenuColor_Text As COLORREF 'テキストの色 MenuColor_Gray_Text As COLORREF '淡色テキストの色 MenuColor_Default_Line As COLORREF '通常時の線の色 MenuColor_Default_Back As COLORREF '通常時の背景色 MenuColor_Hot_Line As COLORREF 'ホット状態の線の色 MenuColor_Hot_Back As COLORREF 'ホット状態の背景色 MenuColor_Select_Line As COLORREF '選択状態の線の色 MenuColor_Select_Back As COLORREF '選択状態の背景色 Public iMenuItemNum As Long ppobj_MenuItemData As **CMenuItemData '------------------------------------------ ' コンストラクタ '※フォント、各種カラーの初期化 '------------------------------------------ Sub CLuxMenu() 'COM参照カウンタの初期化 m_ref=0 'アイテム データ用の領域を初期化 ppobj_MenuItemData=malloc(1) iMenuItemNum=0 'メニューフォント Dim NCMetrics As NONCLIENTMETRICS NCMetrics.cbSize = SizeOf( NONCLIENTMETRICS ) SystemParametersInfo( SPI_GETNONCLIENTMETRICS, sizeof( NONCLIENTMETRICS ), VarPtr(NCMetrics), 0 ) m_hMenuFont=CreateFontIndirect(NCMetrics.lfMenuFont) '各種カラー MenuColor_Text=GetSysColor(COLOR_WINDOWTEXT) 'テキストの色 MenuColor_Gray_Text=GetSysColor(COLOR_GRAYTEXT) '淡色テキストの色 MenuColor_Default_Line=GetSysColor(COLOR_3DDKSHADOW) '通常時の線の色 MenuColor_Default_Back=GetSysColor(COLOR_3DFACE) '通常時の背景色 MenuColor_Hot_Line=GetSysColor(COLOR_ACTIVECAPTION) 'ホット状態の線の色 MenuColor_Hot_Back=LightRGB(MenuColor_Hot_Line,0.7) 'ホット状態の背景色 MenuColor_Select_Line=MenuColor_Default_Line '選択状態の線の色 MenuColor_Select_Back=GetSysColor(COLOR_MENU) '選択状態の背景色 End Sub '------------------------------------------ ' デストラクタ '------------------------------------------ Sub ~CLuxMenu() Dim i As Long For i=0 To ELM(iMenuItemNum) Delete ppobj_MenuItemData[i] Next free(ppobj_MenuItemData) DeleteObject(m_hMenuFont) If m_hMenu Then DestroyMenu(m_hMenu) End Sub ' Note - この部分が各種メソッドの記述領域になります End Class
先ほど説明したCLuxCtrlBaseクラスを継承し、グラデーションビットマップ生成や淡色アイコン生成のメソッドを使えるようにします。
m_refはIUnknown用の参照カウンタです。m_hMenuはメニューハンドル、m_hMenuFontはメニューテキストのフォントハンドルです。MenuColor_???はメニューアイテムの描画時に利用する各種カラーです(詳細はコード内のコメント参照)。ppobj_MenuItemData、iMenuItemNumはメニューアイテムの詳細情報を管理するためのオブジェクトポインタとその個数値です。CMenuItemDataクラスについては後述します。
コンストラクタ、デストラクタではメンバの初期化と解放を行っています。
IUnknownの実装コード
さてCLuxMenuクラスは、後ほどILuxMenuインターフェイスとして提供しなければなりません。COMコンポーネントの義務であるIUnknownは、前回説明したとおりの流れで実装されています。インターフェイスを新たに生成するQueryInterfaceでは、IID_ILuxMenuを識別し、自身への参照が可能になっています。IID_ILuxMenuは後述のILuxMenuインターフェイスと一緒に定義していきます。
'------------------------------------------------------- ' COMインターフェイス用IUnknownメソッド Virtual Function QueryInterface(ByRef riid As IID, ppvObj As *VoidPtr) As HRESULT If IsEqualIID(riid,IID_ILuxMenu)<>0 or _ IsEqualIID(riid,IID_IUnknown)<>0 Then Set_LONG_PTR(ppvObj,VarPtr(This) As LONG_PTR) AddRef() QueryInterface=S_OK Else Set_LONG_PTR(ppvObj,0) QueryInterface=E_NOINTERFACE End If End Function Virtual Function AddRef() As DWord m_ref++ AddRef=m_ref End Function Virtual Function Release() As DWord m_ref-- If m_ref=0 Then Delete VarPtr(This) End If Release=m_ref End Function ' ここまで '-------------------------------------------------------
ここから定義していくメソッドは、COMインターフェイスとして機能させたいものばかりですので、Virtual修飾子を指定して仮想関数にしていきます。
ImportMenuメソッド(一般メニューのインポート)
ImportMenuメソッドによって、Win32APIのCreateMenu関数や、LoadMenu関数で生成されたメニューハンドルを、CLuxMenuクラスがそのまま取り込めるようになっています。こうすることで、既存のメニューからLuxMenuへと、すぐに移行することが可能です。
hMenuパラメータにLuxMenuへ移行したいメニューハンドルが指定されます。GetMenuItemCount関数でメニューアイテムの個数を取得し、その数だけループを行います。ループ内ではAddMenuExItemDataメソッドを利用してCMenuItemDataクラスのインスタンスを、アイテムの個数分だけ生成します。AddMenuExItemDataはPrivateとしてCLuxMenuが持つメソッドです。
サブメニューを持つポップアップアイテムにぶつかったときは、別途CLuxSubMenuクラスのインスタンスを生成しています。サブアイテムの生成先でもImportMenuメソッドを呼び出します(CLuxSubMenuはCLuxMenuの派生クラスなので、ImportMenuを再帰的に呼び出すことができるのです)。
MENUITEMINFO構造体のfTypeメンバに持ちますが、LuxMenuではすべてのメニューアイテムをオーナー描画アイテムとして扱わなければなりません。このループ内では、SetMenuItemInfo関数を呼び出してオーナー描画の指定も行っています。拡張メニュー情報として、MENUITEMINFO構造体のdwItemDataメンバにpobj_MenuItemDataを指定しているところにも注目しましょう。dwItemDataに設定した値は、後ほど解説するWM_MASUREITEM、WM_DRAWITEMメッセージのパラメータとして取得することが可能になります。 ついでなので、GetMenuHandleメソッドの定義もしてしまいましょう。これは外部からCLuxMenuクラスのインスタンスが持っているメニューハンドルを取得するためのものです。
'------------------------------------------------------- '既存メニューのインポート '------------------------------------------------------- Virtual Sub ImportMenu(hMenu As HMENU) '------------------------------------ ' 従来のメニューハンドルの取り込み '------------------------------------ m_hMenu=hMenu Dim temporary[MAX_PATH] As Byte Dim mii As MENUITEMINFO mii.cbSize=SizeOf(MENUITEMINFO) mii.fMask=MIIM_TYPE or MIIM_ID or MIIM_SUBMENU or MIIM_DATA Dim nCount As Long nCount=GetMenuItemCount(m_hMenu) Dim i As Long For i=0 To ELM(nCount) mii.dwTypeData=temporary mii.cch=MAX_PATH GetMenuItemInfo(m_hMenu,i,1,mii) Dim temp2=0 As LPSTR If mii.fType=MFT_STRING Then temp2=temporary 'サブメニューもインポートする Dim pobj_SubMenu As *CLuxSubMenu If mii.hSubMenu Then pobj_SubMenu=New CLuxSubMenu() pobj_SubMenu->ImportMenu(mii.hSubMenu) Else pobj_SubMenu=0 End If '拡張データをメモリに退避 Dim pobj_MenuItemData As *CMenuItemData pobj_MenuItemData=AddMenuExItemData(i,mii.wID, temp2,VarPtr(This),pobj_SubMenu) 'メニューアイテムをすべてオーナー描画可能にする mii.fType=MFT_OWNERDRAW mii.dwItemData=pobj_MenuItemData As ULONG_PTR SetMenuItemInfo(m_hMenu,i,1,mii) Next End Sub '------------------------------------------------------- ' メニューハンドルの取得 '------------------------------------------------------- Virtual Function GetMenuHandle() As HMENU GetMenuHandle=m_hMenu End Function
MeasureItemメソッド(WM_MASUREITEM)、DrawItemメソッド(WM_DRAWITEM)
繰り返しになりますが、LuxMenuは既存メニューコントロールのオーナー描画によって実現されるコントロールです。したがって、ウィンドウからWM_MASUREITEM、WM_DRAWITEMなどのオーナー描画用のメッセージが来たら、しかるべき処理をしなければなりません。そのような機能をウィンドウプロシージャ内に書くのは美しくないので、CLuxMenuが提供する一機能として作成します。
双方のメソッド共に、lParamをパラメータとして持ちます。これでウィンドウプロシージャに渡された値がそのまま送り届けられることになります。パラメータ内の32ビット値にはCMenuItemDataクラスのインスタンスポインタが格納されているので、pobj_MenuItemDataにコピーを行い、CMenuItemDataへのアクセスを可能にしています。
また、WM_MASUREITEM、WM_DRAWITEMメッセージはターゲットとなるメニューアイテムに関係なく送られてくるので、HitTest_MenuObjメソッド(CLuxMenuクラスでPrivate宣言されています)を使って、自身へのメッセージなのかどうかをチェックするための機構を埋め込んでいます。
最後に、MeasureItemメソッドはlpMI->itemWidth、lpMI->itemHeightにメニューアイテムを表示するために必要な幅と高さを指定して終了します。DrawItemメソッドは、CMenuItemData.OwnerDrawメソッドを呼び出して終了します。
'------------------------------------------------------- ' WM_MASUREITEMメッセージの処理 '------------------------------------------------------- Virtual Function MeasureItem(lParam As LPARAM) As BOOL Dim lpMI As *MEASUREITEMSTRUCT lpMI=lParam As *MEASUREITEMSTRUCT If lpMI->CtlType=ODT_MENU Then Dim pobj_MenuItemData As *CMenuItemData pobj_MenuItemData=lpMI->itemData As *CMenuItemData If HitTest_MenuObj(VarPtr(This), pobj_MenuItemData->pobj_ThisMenu As *CLuxMenu) Then Dim size As SIZE pobj_MenuItemData->GetItemSize(size) lpMI->itemWidth=size.cx lpMI->itemHeight=size.cy End If End If MeasureItem=1 End Function '------------------------------------------------------- ' WM_DRAWITEMメッセージの処理 '------------------------------------------------------- Virtual Function DrawItem(lParam As LPARAM) As BOOL Dim pdis As *DRAWITEMSTRUCT pdis=lParam As *DRAWITEMSTRUCT If pdis->CtlType=ODT_MENU Then Dim pobj_MenuItemData As *CMenuItemData pobj_MenuItemData=pdis->itemData As *CMenuItemData If HitTest_MenuObj(VarPtr(This), pobj_MenuItemData->pobj_ThisMenu As *CLuxMenu) Then Dim bSelect As BOOL If pdis->itemState and ODS_SELECTED Then bSelect=1 Else bSelect=0 End If pobj_MenuItemData->OwnerDraw(pdis->hDC, pdis->rcItem, bSelect) End If End If DrawItem=1 End Function
メニュー操作のための各種メソッド
CLuxMenuクラスは以下に示すメソッドを提供します。これらは、Win32APIの各関数の呼び出しを行うことで実現しています。
| メソッド名 | 説明 |
CreateNewMenuメソッド |
メニューコントロールの新規作成 |
EnableItemメソッド |
メニューアイテムを有効化/無効化 |
CheckMenuメソッド |
メニューアイテムのチェックのON/OFF |
IsCheckedメソッド |
メニューアイテムのチェック状況を取得 |
RemoveItemメソッド |
メニューアイテムの消去 |
InsertItemメソッド |
メニューアイテムの挿入 |
InsertPopupItemメソッド |
ポップアップ メニューアイテムの挿入 |
SetIconメソッド
メニューアイテムにアイコンを指定するためのメソッドです。dwItemIDパラメータにメニューアイテムIDを指定し、hIconにアイコンハンドルを指定します。成功すると1が、失敗すると0が返ります。CLuxCtrlBase.CreateGrayIconメソッドを呼び出して自動的に淡色アイコンの生成を行っているのも見逃せない点です。
メニューの深い階層のアイテムも察知できるよう、必要に応じてサブメニューに対してのSetIconメソッドも呼び出しています。
'------------------------------------------------------- ' メニューアイテムにアイコンをセット '------------------------------------------------------- Virtual Function SetIcon(dwItemID As DWord, hIcon As HICON) As BOOL Dim i As Long For i=0 To ELM(iMenuItemNum) If ppobj_MenuItemData[i]->dwItemID=dwItemID Then ppobj_MenuItemData[i]->hIcon=hIcon '淡色アイコンを作成 ppobj_MenuItemData[i]->hGrayIcon=CreateGrayIcon(hIcon) SetIcon=1 Exit Function End If If ppobj_MenuItemData[i]->pobj_SubMenu Then Dim lResult As LRESULT lResult=ppobj_MenuItemData[i]->pobj_SubMenu->SetIcon( _ dwItemID,hIcon) If lResult Then SetIcon=1 Exit Function End If End If Next SetIcon=0 End Function
GetItemSizeメソッド
このメソッドはWM_MEASUREITEMメッセージ→MeasureItemメソッド→GetItemSizeメソッドという流れで呼び出されます。item_indexパラメータで指定されたメニューアイテムのサイズをsizeパラメータに格納します。
'------------------------------------------------------- ' メニューアイテムの表示領域サイズを取得 '------------------------------------------------------- Virtual Sub GetItemSize(item_index As Long, ByRef size As SIZE) If ppobj_MenuItemData[item_index]->str Then Dim hdc As HDC hdc=GetDC(GetDesktopWindow()) Dim hOldFont As HFONT hOldFont=SelectObject(hdc,m_hMenuFont) GetTextExtentPoint32(hdc, ppobj_MenuItemData[item_index]->str, lstrlen(ppobj_MenuItemData[item_index]->str), size) SelectObject(hdc,hOldFont) DeleteDC(hdc) Else 'セパレータのとき size.cx=50 size.cy=10 End If End Sub
OwnerDrawメソッド
CLuxMenu.OwnerDrawメソッドは親メニュー(ウィンドウのメニューバー)の描画を行います。サブメニューの描画については、後ほどCLuxSubMenuでOwnerDrawメソッドをオーバーライドして実装します。
hdcパラメータにはデバイスコンテキストを、ref_rcには描画するためのメニューアイテムの領域を、bSelectにはセレクト状態であるか否かをそれぞれ指定します。
bSelectを使って選択状態を識別し、選択状態であれば選択中の背景を描画します。選択状態でなければ、通常はデフォルトの背景色で塗りつぶしたいところですが、マウスカーソルがアイテム上にあるかどうかをCLuxCtrlBase.HitTestメソッドで判別し、必要に応じてホット状態の描画を行います。
最後に、DrawText関数でメニューアイテムの文字列を描画して完了です。
'------------------------------------------------------- ' 親メニューのオーナー描画 '------------------------------------------------------- Virtual Sub OwnerDraw(hdc As HDC, ByRef ref_rc As RECT, bSelect As BOOL, item_index As Long) Dim BackColor As COLORREF, TextColor As COLORREF Dim rc As RECT Dim pos As POINTAPI Dim hPen As HPEN,hOldPen As HPEN Dim hBrush As HBRUSH, hOldBrush As HBRUSH If bSelect Then '選択状態にある場合 hPen=CreatePen(PS_SOLID,0,MenuColor_Select_Line) hOldPen=SelectObject(hdc,hPen) hBrush=CreateSolidBrush(MenuColor_Select_Back) hOldBrush=SelectObject(hdc,hBrush) ref_rc.left++ ref_rc.top++ Rectangle(hdc,ref_rc.left,ref_rc.top,ref_rc.right,ref_rc.bottom) ref_rc.left-- SelectObject(hdc,hOldPen) SelectObject(hdc,hOldBrush) DeleteObject(hPen) DeleteObject(hBrush) TextColor=MenuColor_Text Else 'マウスカーソルの座標を取得 GetCursorPos(pos) '親ウィンドウのウィンドウ左上からの相対座標に変換 '(クライアント座標ではないところに注意) GetWindowRect(WindowFromDC(hdc),rc) pos.x-=rc.left pos.y-=rc.top If HitTest(ref_rc,pos) Then 'ホットイメージで表示 hPen=CreatePen(PS_SOLID,0,MenuColor_Hot_Line) hOldPen=SelectObject(hdc,hPen) hBrush=CreateSolidBrush(MenuColor_Hot_Back) hOldBrush=SelectObject(hdc,hBrush) ref_rc.left++ ref_rc.top++ Rectangle(hdc,ref_rc.left,ref_rc.top,ref_rc.right,ref_rc.bottom) ref_rc.left-- ref_rc.top-- SelectObject(hdc,hOldPen) SelectObject(hdc,hOldBrush) DeleteObject(hPen) DeleteObject(hBrush) TextColor=MenuColor_Text Else BackColor=MenuColor_Default_Back TextColor=MenuColor_Text '背景を塗りつぶす hBrush=CreateSolidBrush(BackColor) FillRect(hdc,ref_rc,hBrush) DeleteObject(hBrush) End If End If If ppobj_MenuItemData[item_index]->str Then rc=ref_rc rc.top++ SetBkMode(hdc, TRANSPARENT) SetTextColor(hdc, TextColor) Dim hOldFont As HFONT hOldFont=SelectObject(hdc,m_hMenuFont) DrawText(hdc,ppobj_MenuItemData[item_index]->str,-1, rc,DT_CENTER or DT_VCENTER or DT_SINGLELINE) SelectObject(hdc,hOldFont) Else 'セパレータのとき End If End Sub
