一、前言
.NET框架是Windows應用領域中一個非常新的技術,可以肯定在未來的一段時間內,.NET應用必須與現存的Windows技術交互作用。這種交互作用主要體現在兩個領域:COM和應用編程接口(API)。為此,.NET框架在Windows API之上提供了一個OO層,但是有時候可能需要使用一個.NET不可到達的API調用。在這種情況下,可以使用.NET平台調用(P/Invoke)機制從.NET中調用C或C++函數。因為Windows API函數在DLL中,所以,P/Invoke為從.NET代碼調用DLL中的C或C++函數提供了一種通用機制。
本文針對C#.NET中沒有提供直接的類似SystemMenu的屬性或類似GetSystemMenu的成員函數的實際,編寫了一個C#類SystemMenu,從而實現了傳統的對於系統菜單的操作,這是通過調用本地Windows API來完成的。
二、系統菜單簡介
當你單擊窗口圖標或右擊窗口標題欄時系統菜單即彈出。它包含當前窗口的默認行為。不同窗口的系統菜單看起來有些不同,如一個正常的窗口的系統菜單看起來與一個工具欄子對話框窗口的菜單就不一樣。
修改系統菜單的好處:
·添加應用程序自己定義的菜單項。
·在WW被最小化時,SS是一個很好的地方來放置動作,可以被存取,因為SS可以顯示,通過在任務欄窗口圖標上單擊右鍵。
·使某菜單項失去能力,如從系統菜單中移去“最大化”,“最小化”“關閉”等。由於這種改動還影響到窗口右上角的三個按鈕,所以這是一個使窗口右上角“X”失去能力的不錯的辦法。
操縱系統菜單
通過調用 API函數GetSystemMenu,你就檢索到了系統菜單的一個拷貝。該函數的第二個參數指明是否你要復位系統菜單到它的缺省狀態。再加上另外幾個API菜單函數如AppendMenu, InsertMenu等,你就能實現對於系統菜單的靈活控制。
下面我僅簡單介紹如何添加菜單項以及如何實現新項與用戶的交互。
三、SystemMenu 類介紹
SystemMenu類的實現使得整個系統菜單存取容易許多。你可以使用這個類來修改一個窗口的菜單。 通過調用靜態成員函數FromForm你得到一個對象,該函數要求一個Form對象或一個從Form繼承的類作為它的參數。然後它創建一個新的對象,當然如果GetSystemMenu API調用失敗的話,將引發一個NoSystemMenuException例外。
注意,每一個Windows API菜單函數要求一個菜單句柄以利於操作。因為菜單句柄實際上是一個C++指針,所以在.NET中你要使用IntPtr來操作它。許多函數還需要一個位掩碼標志來指明新菜單項的動作或形式。幸運的是,你不必象在VC++中那樣,通過某個頭文件的包含來使用一系列的位掩碼標志定義,.NET中已經提供了一個現成的公共枚舉類ItemFlags。下面對這個類的幾個重要成員作一說明:
·mfString―― 告訴子系統將顯示由菜單項中的“Item”參數傳遞的字符串。
·mfSeparator――此時 "ID" 與 "Item" 參數被忽略。
·MfBarBreak―― 當用於菜單條時,其功能與mfBreak一樣;當用於下拉菜單,子菜單或快捷菜單時,新的一列與舊有的一列由一線垂直線所隔開。
·MfBreak――把當前項目放在一個新行(菜單條)或新的一列(下拉菜單,子菜單或快捷菜單)。
注意:如果指定多個標志,應該用位操作運算符|(或)連接。例如:
//將創建一個菜單項 "Test" ,且該項被選中(checked)
mySystemMenu.AppendMenu(myID, "Test", ItemFlags.mfString|ItemFlags.mfChecked);
“Item”參數指定了新項中要顯示的文本,其ID必須是唯一的數字――用來標志該菜單項。
注意:確保新項的ID大於0小於0XF000。因為大於等於0XF000的范圍為系統命令所保留使用。你也可以調用類SystemMenu的靜態方法VerifyItemID來核驗是否你的ID正確。
另外,還有兩個需要解釋的常量:mfByCommand和mfByPosition。
第一,在缺省情況下,使用mfByCommand。第二,“Pos”的解釋依賴於這些標志:如果你指定mfByCommand,“Pos”參數就是在新項目插入前項目的ID;如果你指定mfByPosition,“Pos”參數就是以0索引為開頭的新項的相對位置;如果是-1並且指定mfByPosition,該項目將被插入到最後。這也正是為什麼AppendMenu()可以為InsertMenu()所取代的原因。
四、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