程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C#+Windows API操縱系統菜單

C#+Windows API操縱系統菜單

編輯:關於C語言
 一、前言

  本文針對C#.Net中沒有提供直接的類似SystemMenu的屬性或類似GetSystemMenu的成員函數的情況,通過調用Windows API設計了一個C#類SystemMenu,從而實現了傳統的對於系統菜單的操作。

  二、系統菜單簡介

  當你單擊窗口圖標或右擊窗口標題欄時系統菜單即彈出。它包含當前窗口的默認行為。不同窗口的系統菜單看起來有些不同,如一個正常窗口的系統菜單看起來與一個工具欄子對話框窗口的菜單就不一樣。

  修改系統菜單的好處:

  ·添加應用程序自己定義的菜單項。

  ·在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);
}

// 檢查是否一個給定的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下調試通過。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved