四、SystemMenu 類代碼分析
using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
public class NoSystemMenuException : System.Exception
{}
//這些值來自於MSDN
public enum ItemFlags
{
// The item ...
mfUnchecked = 0x00000000, // ... is not checked
mfString = 0x00000000, // ... contains a string as label
mfDisabled = 0x00000002, // ... is disabled
mfGrayed = 0x00000001, // ... is grayed
mfChecked = 0x00000008, // ... is checked
mfPopup = 0x00000010, // ... Is a popup menu. Pass the
// menu handle of the popup
// menu into the ID parameter.
mfBarBreak = 0x00000020, // ... is a bar break
mfBreak = 0x00000040, // ... is a break
mfByPosition = 0x00000400, // ... is identifIEd by the position
mfByCommand = 0x00000000, // ... is identifIEd by its ID
mfSeparator = 0x00000800 // ... is a seperator (String and
// ID parameters are ignored).
}
public enum WindowMessages
{
wmSysCommand = 0x0112
}
//
/// 幫助實現操作系統菜單的類的定義
///.
//注意:用P/Invoke調用動態鏈接庫中非托管函數時,應執行如下步驟:
//1,定位包含該函數的DLL。
//2,把該DLL庫裝載入內存。
//3,找到即將調用的函數地址,並將所有的現場壓入堆棧。
//4,調用函數。
//
public class SystemMenu
{
// 提示:C#把函數聲明為外部的,而且使用屬性DllImport來指定DLL
//和任何其他可能需要的參數。
// 首先,我們需要GetSystemMenu() 函數
// 注意這個函數沒有Unicode 版本
[DllImport("USER32", EntryPoint="GetSystemMenu", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private static extern IntPtr apiGetSystemMenu(IntPtr WindowHandle,int bReset);
// 還需要AppendMenu()。 既然 .Net 使用Unicode,
// 我們應該選取它的Unicode版本。
[DllImport("USER32", EntryPoint="AppendMenuW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private static extern int apiAppendMenu( IntPtr MenuHandle, int Flags,int NewID, String Item );
//還可能需要InsertMenu()
[DllImport("USER32", EntryPoint="InsertMenuW", SetLastError=true,
CharSet=CharSet.Unicode, ExactSpelling=true,
CallingConvention=CallingConvention.Winapi)]
private static extern int apiInsertMenu ( IntPtr hMenu, int Position,int Flags, int NewId,String Item );
private IntPtr m_SysMenu = IntPtr.Zero; // 系統菜單句柄
public SystemMenu( )
{}
// 在給定的位置(以0為索引開始值)插入一個分隔條
public bool InsertSeparator ( int Pos )
{
return ( InsertMenu(Pos, ItemFlags.mfSeparator |ItemFlags.mfByPosition, 0, "") );
}
// 簡化的InsertMenu(),前提――Pos參數是一個0開頭的相對索引位置
public bool InsertMenu ( int Pos, int ID, String Item )
{
return ( InsertMenu(Pos, ItemFlags.mfByPosition |ItemFlags.mfString, ID, Item) );
}
// 在給定位置插入一個菜單項。具體插入的位置取決於Flags
public bool InsertMenu ( int Pos, ItemFlags Flags, int ID, String Item )
{
return ( apiInsertMenu(m_SysMenu, Pos, (Int32)Flags, ID, Item) == 0);
}
// 添加一個分隔條
public bool AppendSeparator ( )
{
return AppendMenu(0, "", ItemFlags.mfSeparator);
}
// 使用ItemFlags.mfString 作為缺省值
public bool AppendMenu ( int ID, String Item )
{
return AppendMenu(ID, Item, ItemFlags.mfString);
}
// 被取代的函數
public bool AppendMenu ( int ID, String Item, ItemFlags Flags )
{
return ( apiAppendMenu(m_SysMenu, (int)Flags, ID, Item) == 0 );
}
//從一個Form對象檢索一個新對象
public static SystemMenu FromForm ( Form Frm )
{
SystemMenu cSysMenu = new SystemMenu();
cSysMenu.m_SysMenu = apiGetSystemMenu(Frm.Handle, 0);
if ( cSysMenu.m_SysMenu == IntPtr.Zero )
{ // 一旦失敗,引發一個異常
throw new NoSystemMenuException();
}
return cSysMenu;
}
// 當前窗口菜單還原 public static void ResetSystemMenu ( Form Frm )
{
apiGetSystemMenu(Frm.Handle, 1);
}
// 檢查是否一個給定的ID在系統菜單ID范圍之內
public static bool VerifyItemID ( int ID )
{
return (bool)( ID < 0xF000 && ID > 0 );
}
}
你可以使用靜態方法ResetSystemMenu把窗口的系統菜單設置為原來狀態――這在應用程序遇到錯誤或沒有正確修改菜單時是很有用的。
五、使用SystemMenu類
// SystemMenu 對象
private SystemMenu m_SystemMenu = null;
// ID 常數定義
private const int m_AboutID = 0x100;
private const int m_ResetID = 0x101;
private void frmMain_Load(object sender, System.EventArgs e)
{
try
{
m_SystemMenu = SystemMenu.FromForm(this);
// 添加一個separator ...
m_SystemMenu.AppendSeparator();
// 添加"關於" 菜單項
m_SystemMenu.AppendMenu(m_AboutID, "關於");
// 在菜單頂部加上"復位"菜單項
m_SystemMenu.InsertSeparator(0);
m_SystemMenu.InsertMenu(0, m_ResetID, "復位系統菜單");
}
catch ( NoSystemMenuException /* err */ )
{
// 建立你的錯誤處理器
}
}
六、檢測自定義的菜單項是否被點擊
這是較難實現的部分。因為你必須重載你的從Form或Control繼承類的WndProc成員函數。你可以這樣實現:
protected override void WndProc ( ref Message msg )
{
base.WndProc(ref msg);
}
注意,必須調用基類的WndProc實現;否則,不能正常工作。
現在,我們來分析一下如何重載WndProc。首先應該截獲WM_SYSCOMMAND消息。當用戶點擊系統菜單的某一項或者選擇“最大化”按鈕,“最小化”按鈕或者“關閉”按鈕時,我們要檢索該消息。特別注意,消息對象的WParam參數正好包含了被點擊菜單項的ID。於是我們可以實現如下重載:
protected override void WndProc ( ref Message msg )
{
// 通過截取WM_SYSCOMMAND消息並進行處理
// 注意,消息WM_SYSCOMMAND被定義在WindowMessages枚舉類中
// 消息的WParam參數包含點擊的項的ID
// 該值與通過上面類的InsertMenu()或AppendMenu()成員函數傳遞的一樣
if ( msg.Msg == (int)WindowMessages.wmSysCommand )
{
switch ( msg.WParam.ToInt32() )
{
case m_ResetID: // reset菜單項的ID
{
if ( MessageBox.Show(this, "\tAre you sure?","Question", MessageBoxButtons.YesNo) ==
DialogResult.Yes )
{ // 復位系統菜單
SystemMenu.ResetSystemMenu(this);
}
} break;
case m_AboutID:
{ // “關於”菜單項
MessageBox.Show(this, "作者: 朱先中 \n\n "+"e-mail: [email protected]", "關於");
} break;
// 這裡可以針對另外的菜單項設計處理過程
}
}
// 調用基類函數
base.WndProc(ref msg);
}
七、總結
實現上述目標的另一個可能的方法是,通過創建一個事件OnSysCommand並當消息WM_SYSCOMMAND傳來時激活它,然後把屬性WParam傳遞給該事件的句柄。讀者可以自行編程驗證。
總之,本文通過一個簡單的系統菜單修改例子,分析了用C#使用.NET平台調用機制來調用DLL中的非托管函數的基本步驟及注意事項。另,所附源程在Windows2000 Server/ VS .Net2003下調試通過。