一、前言
本文針對(duì)c#.net中沒(méi)有提供直接的類(lèi)似systemmenu的屬性或類(lèi)似getsystemmenu的成員函數(shù)的情況,通過(guò)調(diào)用windows api設(shè)計(jì)了一個(gè)c#類(lèi)systemmenu,從而實(shí)現(xiàn)了傳統(tǒng)的對(duì)于系統(tǒng)菜單的操作。
二、系統(tǒng)菜單簡(jiǎn)介
當(dāng)你單擊窗口圖標(biāo)或右擊窗口標(biāo)題欄時(shí)系統(tǒng)菜單即彈出。它包含當(dāng)前窗口的默認(rèn)行為。不同窗口的系統(tǒng)菜單看起來(lái)有些不同,如一個(gè)正常窗口的系統(tǒng)菜單看起來(lái)與一個(gè)工具欄子對(duì)話(huà)框窗口的菜單就不一樣。
修改系統(tǒng)菜單的好處:
·添加應(yīng)用程序自己定義的菜單項(xiàng)。
·在ww被最小化時(shí),ss是一個(gè)很好的地方來(lái)放置動(dòng)作,可以被存取,因?yàn)閟s可以顯示,通過(guò)在任務(wù)欄窗口圖標(biāo)上單擊右鍵。
·使某菜單項(xiàng)失去能力,如從系統(tǒng)菜單中移去“最大化”,“最小化”“關(guān)閉”等。由于這種改動(dòng)還影響到窗口右上角的三個(gè)按鈕,所以這是一個(gè)使窗口右上角“x”失去能力的不錯(cuò)的辦法。
操縱系統(tǒng)菜單
通過(guò)調(diào)用 api函數(shù)getsystemmenu,你就檢索到了系統(tǒng)菜單的一個(gè)拷貝。該函數(shù)的第二個(gè)參數(shù)指明是否你要復(fù)位系統(tǒng)菜單到它的缺省狀態(tài)。再加上另外幾個(gè)api菜單函數(shù)如appendmenu, insertmenu等,你就能實(shí)現(xiàn)對(duì)于系統(tǒng)菜單的靈活控制。
下面我僅簡(jiǎn)單介紹如何添加菜單項(xiàng)以及如何實(shí)現(xiàn)新項(xiàng)與用戶(hù)的交互。
三、systemmenu 類(lèi)介紹
systemmenu類(lèi)的實(shí)現(xiàn)使得整個(gè)系統(tǒng)菜單存取變得非常容易。你可以使用這個(gè)類(lèi)來(lái)修改一個(gè)窗口的菜單。 通過(guò)調(diào)用靜態(tài)成員函數(shù)fromform你得到一個(gè)對(duì)象,該函數(shù)要求一個(gè)form對(duì)象或一個(gè)從form繼承的類(lèi)作為它的參數(shù)。然后它創(chuàng)建一個(gè)新的對(duì)象,當(dāng)然如果getsystemmenu api調(diào)用失敗的話(huà),將引發(fā)一個(gè)nosystemmenuexception例外。
注意,每一個(gè)windows api菜單函數(shù)要求一個(gè)菜單句柄以利于操作。因?yàn)椴藛尉浔鷮?shí)際上是一個(gè)c++指針,所以在.net中你要使用intptr來(lái)操作它。許多函數(shù)還需要一個(gè)位掩碼標(biāo)志來(lái)指明新菜單項(xiàng)的動(dòng)作或形式。幸運(yùn)的是,你不必象在vc++中那樣,通過(guò)某個(gè)頭文件的包含來(lái)使用一系列的位掩碼標(biāo)志定義,.net中已經(jīng)提供了一個(gè)現(xiàn)成的公共枚舉類(lèi)itemflags。下面對(duì)這個(gè)類(lèi)的幾個(gè)重要成員作一說(shuō)明:
·mfstring―― 告訴子系統(tǒng)將顯示由菜單項(xiàng)中的“item”參數(shù)傳遞的字符串。
·mfseparator――此時(shí) "id" 與 "item" 參數(shù)被忽略。
·mfbarbreak―― 當(dāng)用于菜單條時(shí),其功能與mfbreak一樣;當(dāng)用于下拉菜單,子菜單或快捷菜單時(shí),新的一列與舊有的一列由一線(xiàn)垂直線(xiàn)所隔開(kāi)。
·mfbreak――把當(dāng)前項(xiàng)目放在一個(gè)新行(菜單條)或新的一列(下拉菜單,子菜單或快捷菜單)。
注意:如果指定多個(gè)標(biāo)志,應(yīng)該用位操作運(yùn)算符|(或)連接。例如:
//將創(chuàng)建一個(gè)菜單項(xiàng) "test" ,且該項(xiàng)被選中(checked)
mysystemmenu.appendmenu(myid, "test", itemflags.mfstring |itemflags.mfchecked);
“item”參數(shù)指定了新項(xiàng)中要顯示的文本,其id必須是唯一的數(shù)字――用來(lái)標(biāo)志該菜單項(xiàng)。
注意:確保新項(xiàng)的id大于0小于0xf000。因?yàn)榇笥诘扔?xf000的范圍為系統(tǒng)命令所保留使用。你也可以調(diào)用類(lèi)systemmenu的靜態(tài)方法verifyitemid來(lái)核驗(yàn)是否你的id正確。
另外,還有兩個(gè)需要解釋的常量:mfbycommand和mfbyposition。
第一,在缺省情況下,使用mfbycommand。第二,“pos”的解釋依賴(lài)于這些標(biāo)志:如果你指定mfbycommand,“pos”參數(shù)就是在新項(xiàng)目插入前項(xiàng)目的id;如果你指定mfbyposition,“pos”參數(shù)就是以0索引為開(kāi)頭的新項(xiàng)的相對(duì)位置;如果是-1并且指定mfbyposition,該項(xiàng)目將被插入到最后。這也正是為什么appendmenu()可以為insertmenu()所取代的原因。 四、systemmenu 類(lèi)代碼分析
using system;
using system.windows.forms;
using system.diagnostics;
using system.runtime.interopservices;
public class nosystemmenuexception : system.exception
{}
//這些值來(lái)自于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
}
//
/// 幫助實(shí)現(xiàn)操作系統(tǒng)菜單的類(lèi)的定義
///.
//注意:用p/invoke調(diào)用動(dòng)態(tài)鏈接庫(kù)中非托管函數(shù)時(shí),應(yīng)執(zhí)行如下步驟:
//1,定位包含該函數(shù)的dll。
//2,把該dll庫(kù)裝載入內(nèi)存。
//3,找到即將調(diào)用的函數(shù)地址,并將所有的現(xiàn)場(chǎng)壓入堆棧。
//4,調(diào)用函數(shù)。
//
public class systemmenu
{
// 提示:c#把函數(shù)聲明為外部的,而且使用屬性dllimport來(lái)指定dll
//和任何其他可能需要的參數(shù)。
// 首先,我們需要getsystemmenu() 函數(shù)
// 注意這個(gè)函數(shù)沒(méi)有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,
// 我們應(yīng)該選取它的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; // 系統(tǒng)菜單句柄
public systemmenu( )
{}
// 在給定的位置(以0為索引開(kāi)始值)插入一個(gè)分隔條
public bool insertseparator ( int pos )
{
return ( insertmenu(pos, itemflags.mfseparator |itemflags.mfbyposition, 0, "") );
}
// 簡(jiǎn)化的insertmenu(),前提――pos參數(shù)是一個(gè)0開(kāi)頭的相對(duì)索引位置
public bool insertmenu ( int pos, int id, string item )
{
return ( insertmenu(pos, itemflags.mfbyposition |itemflags.mfstring, id, item) );
}
// 在給定位置插入一個(gè)菜單項(xiàng)。具體插入的位置取決于flags
public bool insertmenu ( int pos, itemflags flags, int id, string item )
{
return ( apiinsertmenu(m_sysmenu, pos, (int32)flags, id, item) == 0);
}
// 添加一個(gè)分隔條
public bool appendseparator ( )
{
return appendmenu(0, "", itemflags.mfseparator);
}
// 使用itemflags.mfstring 作為缺省值
public bool appendmenu ( int id, string item )
{
return appendmenu(id, item, itemflags.mfstring);
}
// 被取代的函數(shù)
public bool appendmenu ( int id, string item, itemflags flags )
{
return ( apiappendmenu(m_sysmenu, (int)flags, id, item) == 0 );
}
//從一個(gè)form對(duì)象檢索一個(gè)新對(duì)象
public static systemmenu fromform ( form frm )
{
systemmenu csysmenu = new systemmenu();
csysmenu.m_sysmenu = apigetsystemmenu(frm.handle, 0);
if ( csysmenu.m_sysmenu == intptr.zero )
{ // 一旦失敗,引發(fā)一個(gè)異常
throw new nosystemmenuexception();
}
return csysmenu;
}
// 當(dāng)前窗口菜單還原 public static void resetsystemmenu ( form frm )
{
apigetsystemmenu(frm.handle, 1);
}
// 檢查是否一個(gè)給定的id在系統(tǒng)菜單id范圍之內(nèi)
public static bool verifyitemid ( int id )
{
return (bool)( id < 0xf000 && id > 0 );
}
}
你可以使用靜態(tài)方法resetsystemmenu把窗口的系統(tǒng)菜單設(shè)置為原來(lái)狀態(tài)――這在應(yīng)用程序遇到錯(cuò)誤或沒(méi)有正確修改菜單時(shí)是很有用的。
五、使用systemmenu類(lèi)
// systemmenu 對(duì)象
private systemmenu m_systemmenu = null;
// id 常數(shù)定義
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);
// 添加一個(gè)separator ...
m_systemmenu.appendseparator();
// 添加"關(guān)于" 菜單項(xiàng)
m_systemmenu.appendmenu(m_aboutid, "關(guān)于");
// 在菜單頂部加上"復(fù)位"菜單項(xiàng)
m_systemmenu.insertseparator(0);
m_systemmenu.insertmenu(0, m_resetid, "復(fù)位系統(tǒng)菜單");
}
catch ( nosystemmenuexception /* err */ )
{
// 建立你的錯(cuò)誤處理器
}
}
六、檢測(cè)自定義的菜單項(xiàng)是否被點(diǎn)擊
這是較難實(shí)現(xiàn)的部分。因?yàn)槟惚仨氈剌d你的從form或control繼承類(lèi)的wndproc成員函數(shù)。你可以這樣實(shí)現(xiàn):
protected override void wndproc ( ref message msg )
{
base.wndproc(ref msg);
}
注意,必須調(diào)用基類(lèi)的wndproc實(shí)現(xiàn);否則,不能正常工作。
現(xiàn)在,我們來(lái)分析一下如何重載wndproc。首先應(yīng)該截獲wm_syscommand消息。當(dāng)用戶(hù)點(diǎn)擊系統(tǒng)菜單的某一項(xiàng)或者選擇“最大化”按鈕,“最小化”按鈕或者“關(guān)閉”按鈕時(shí),我們要檢索該消息。特別注意,消息對(duì)象的wparam參數(shù)正好包含了被點(diǎn)擊菜單項(xiàng)的id。于是我們可以實(shí)現(xiàn)如下重載:
protected override void wndproc ( ref message msg )
{
// 通過(guò)截取wm_syscommand消息并進(jìn)行處理
// 注意,消息wm_syscommand被定義在windowmessages枚舉類(lèi)中
// 消息的wparam參數(shù)包含點(diǎn)擊的項(xiàng)的id
// 該值與通過(guò)上面類(lèi)的insertmenu()或appendmenu()成員函數(shù)傳遞的一樣
if ( msg.msg == (int)windowmessages.wmsyscommand )
{
switch ( msg.wparam.toint32() )
{
case m_resetid: // reset菜單項(xiàng)的id
{
if ( messagebox.show(this, "/tare you sure?","question", messageboxbuttons.yesno) ==
dialogresult.yes )
{ // 復(fù)位系統(tǒng)菜單
systemmenu.resetsystemmenu(this);
}
} break;
case m_aboutid:
{ // “關(guān)于”菜單項(xiàng)
messagebox.show(this, "作者: 朱先中 /n/n "+"e-mail: [email protected]", "關(guān)于");
} break;
// 這里可以針對(duì)另外的菜單項(xiàng)設(shè)計(jì)處理過(guò)程
}
}
// 調(diào)用基類(lèi)函數(shù)
base.wndproc(ref msg);
}
七、總結(jié)
實(shí)現(xiàn)上述目標(biāo)的另一個(gè)可能的方法是,通過(guò)創(chuàng)建一個(gè)事件onsyscommand并當(dāng)消息wm_syscommand傳來(lái)時(shí)激活它,然后把屬性wparam傳遞給該事件的句柄。讀者可以自行編程驗(yàn)證。
總之,本文通過(guò)一個(gè)簡(jiǎn)單的系統(tǒng)菜單修改例子,分析了用c#使用.net平臺(tái)調(diào)用機(jī)制來(lái)調(diào)用dll中的非托管函數(shù)的基本步驟及注意事項(xiàng)。另,所附源程在windows2000 server/ vs .net2003下調(diào)試通過(guò)。