public void Start(Form form)
{
// 從 Start 傳入參數取得該視窗主選單
// 為每個主選單下 MenuItem 新增 MeasureItem 事件處理 mainMenuItem_MeasureItem
// 和 DrawItem 事件處理 mainMenuItem_DrawItem
// 將 MenuItem OwnerDraw 屬性設為 true,並用 InitMenuItem(Menu mi) 方法對每個 MenuItem 應用變更
// 然後從 form 參數取得該視窗內容選單物件,呼叫 InitMenuItem(Menu mi) 應用變更
// 最後迴圈查找視窗包含每個子控制項內容選單,應用變更
System.Windows.Forms.MainMenu menu = form.Menu;
foreach (MenuItem mi in menu.MenuItems)
{
mi.MeasureItem += new System.Windows.Forms.MeasureItemEventHandler(mainMenuItem_MeasureItem);
mi.DrawItem += new System.Windows.Forms.DrawItemEventHandler(mainMenuItem_DrawItem);
mi.OwnerDraw = true;
InitMenuItem(mi);
}
ContextMenu cmenu = form.ContextMenu;
if (cmenu != null) { InitMenuItem(cmenu); }
foreach (Control c in form.Controls)
{
if (c.ContextMenu != null)
InitMenuItem(c.ContextMenu);
}
}
End 方法類似,只須將 InitMenuItem 換為 UninitMenuItem,此處不再列出。具體實作代碼請至 CodeProject 下載。
private void InitMenuItem(Menu mi)
{
// 迴圈查找 mi 中每個 MenuItem,新增 MeasureItem 事件處理 menuItem_MeasureItem
// 和 DrawItem 事件處理 menuItem_DrawItem (注意:此處事件處理方法名稱與 Start 中對主選單不同)
// 將 OwnerDraw 屬性設為 true
// 使用遞迴呼叫對每個查找到的 MenuItem 呼叫 InitMenuItem 方法,這樣便可對選單項下所有層級子選單項應用變更
}
UninitMenuItem 定義類似。
private void menuItem_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e)
{
// 取得要進行訊息處理 MenuItem 物件 (MenuItem item = (MenuItem)sender;)
// if (item 為 separator) { e.ItemHeight = 7; }
// else {
// 取得 item 文字寬度,若有快速鍵,還要取得 item 中快速鍵佔用寬度
// 設定 item 邊界:e.ItemHeight = 文字高度 + 7; e.ItemWidth = 文字寬度 + 快速鍵寬度 + 圖標寬度 * 2;
// }
}
private void menuItem_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
MenuItemDrawing.DrawMenuItem(e, (MenuItem)sender);
}
該函式只是簡單呼叫 MenuItemDrawing 類別靜態方法 DrawMenuItem。
private void mainMenuItem_MeasureItem(object sender, System.Windows.Forms.MeasureItemEventArgs e)
{
MenuItem mi = (MenuItem)sender; // 取得選單項物件
SizeF miSize = e.Graphics.MeasureString(mi.Text, Globals.menuFont);
// 因頂層選單 (如檔案選單) 無快速鍵與圖標,繪製寬度為文字寬度
e.ItemWidth = Convert.ToInt32(miSize.Width);
}
private void mainMenuItem_DrawItem(object sender, System.Windows.Forms.DrawItemEventArgs e)
{
MainMenuItemDrawing.DrawMenuItem(e, (MenuItem)sender);
}
該方法只是呼叫 MainMenuItemDrawing 類別靜態方法 DrawMenuItem 進行選單繪製。
public void AddPicture(MenuItem mi, int index)
{
// 將選單項控制代碼轉為字串與圖標索引一一對應加入 picDetails 集合
picDetails.Add(mi.Handle.ToString(), index.ToString());
}
public static Bitmap GetItemPicture(MenuItem mi)
{
if (_imageList == null) return null;
// 以選單項控制代碼為鍵查找對應值,回傳值為圖標索引
string[] picIndex = picDetails.GetValues(mi.Handle.ToString());
if (picIndex == null) return null;
else
// 根據索引取出位圖物件並回傳
return (Bitmap)_imageList.Images[Convert.ToInt32(picIndex[0])];
}
MainMenuItemDrawing 主要負責主選單一級子選單 (如常見檔案選單) 繪製:
public class MainMenuItemDrawing
{
public static void DrawMenuItem(System.Windows.Forms.DrawItemEventArgs e, MenuItem mi)
{
// 首先檢查 menuItem 狀態,如為滑鼠懸浮則呼叫 DrawHoverRect 繪製並填充懸浮矩形
// 如為選定態則呼叫 DrawSelectionRect 繪製並填充相應選定態矩形
// 如兩者都非則用控制項填充色繪製並填充矩形
// 最後利用 e.Graphics.DrawString 方法繪製選單文字
}
// ...
}
MenuItemDrawing 負責主選單子選單與內容選單繪製:
public class MenuItemDrawing
{
public static void DrawMenuItem(System.Windows.Forms.DrawItemEventArgs e, MenuItem mi)
{
// 檢查選單項是否被選中,如被選中則呼叫 DrawSelectionRect 繪製並填充選中後矩形
// 否則只用背景色繪製空白區域並呼叫 DrawPictureArea 繪製圖片區域
// 呼叫 DrawCheckBox 繪製核取方塊若該選單項被選中
// 呼叫 DrawMenuText 繪製選單項文字
// 最後呼叫 DrawItemPicture 繪製圖標
}
// ...
}
因繪製代碼較長,不宜全文給出,故省略具體繪製代碼,僅給出實現元件思路。
因 .NET 使用 GDI+,元件繪製工作比以往 MFC 或 Win32 API 模式容易許多。元件開發最重要點:當選單項設為自繪方式後,使用者需啟用兩個事件定制選單顯示。第一事件對應 Win32 WM_MEASUREITEM 訊息,視窗收到此訊息觸發 MeasureItem 事件給所有自繪 MenuItem 物件。此事件委派為 MeasureItemEventHandler 類別,相關資訊儲存於 MeasureItemEventArgs 並傳遞給事件處理函式。第二事件對應 Win32 WM_DRAWITEM 訊息,給每個註冊事件處理函式傳遞 DrawItemEventArgs。此事件委派為 DrawItemEventHandler 類別。個人認為元件實現難點與重點即在兩事件處理函式中根據選單不同狀態進行不同繪製工作。