不熟悉API使得我為了實現一個簡單的功能浪費了很長的時間,下面就把自己查閱的相關東西做個總結:
常用的處理表單的API函數如下(注意:API函數必須放在表單中...):
使用C#語言,要引用DllImport,必須要添加using System.Runtime.InteropServices命名空間
(1)獲得當前前台表單控制代碼
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetForegroundWindow();
傳回值類型是IntPtr,即為當前獲得焦點視窗的控制代碼
使用方法 : IntPtr myPtr=GetForegroundWindow();
(2)枚舉所有螢幕上的頂層視窗,並將視窗控制代碼傳送給應用程式定義的回呼函數,利用該法可以獲得所有當前開啟的表單的控制代碼資訊
[DllImport("user32.dll")]
public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc,LPARAM lParam);
其中lpEnumFunc指向一個應用程式定義的回呼函數指標;
lparam指向一個傳遞給回呼函數的應用程式的定義值;
回呼函數原型
bool CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lparam);
其中hwnd是一個頂層視窗的控制代碼
lparam是一個應用程式定義的一個值(即EnumWindows中lParam)
下面用一個例子對該方法說明
程式中要實現一個功能:可以在當前開啟的表單中找到目標表單,並在需要時將其啟用,置為前台表單
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.InteropServices;//調用DLLIMPORTnamespace EmuWindowInfor{ /// <summary> /// 調用API的EnumWindows來枚舉視窗 /// </summary> class Program {
//定義控制代碼的全域變數
public int HANDLE; //定義回呼函數的委託 public delegate bool CALLBACK(int hwnd,int lparm); //用於擷取前台視窗控制代碼,設定當前視窗控制代碼 [DllImport("user32.dll")] public static extern int EnumWindows(CALLBACK x, int y); [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int GetWindowText(int hWnd, StringBuilder lpText, int nCount); static void Main(string[] args) { CALLBACK myCallBack = new CALLBACK(Report); EnumWindows(myCallBack, 0); Console.ReadKey(); } //執行個體化回呼函數(可以在回呼函數中根據表單名稱找到目標表單控制代碼) public static bool Report(int hwnd,int lparm) { //分配空間 var sb = new StringBuilder(50); GetWindowText(hwnd, sb, sb.Capacity); //注意某些視窗沒有標題 if (sb.ToString() != String.Empty) Console.WriteLine(sb.ToString()); //if (sb.ToString() == "Microsoft PowerPoint - [les_03_使用_rman [相容模式]]") // Console.WriteLine(hwnd.ToString()); //回呼函數有傳回值 return true; } }}
以上代碼實現了通過表單名稱找到目標表單的控制代碼,再利用API函數SetForegroundWindow來將該表單啟用並前置
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(int hWnd);
其中hWnd就是目標表單的控制代碼
(3)根據表單的類名和視窗的名稱獲得目標表單
[DllImport("coredll.dll", EntryPoint = "FindWindow")]
private extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
其中lpClassName是要找的視窗的類
lpWindowName是要找的視窗的標題,當然在搜尋的時候不一定兩者都要知道,但至少要知道一個。根據視窗標題尋找的一般多用在多視窗的應用程式中,因為程式中的表單標題固定,方便搜尋。但要是對於系統中
的一些表單,例如記事本表單,PPT等,表單的標題是不定的,所以用視窗類別搜尋更方便。當然有關常見的視窗類別可以很方便找到,下面是一個搜尋當前開啟文字文件的視窗控制代碼的代碼
IntPtr ParenthWnd = new IntPtr(0); ParenthWnd = FindWindow(null,"Word Mobile"); //判斷這個表單是否有效 if (ParenthWnd != IntPtr.Zero) { MessageBox.Show("找到視窗"); } else MessageBox.Show("沒有找到視窗");
如果用VC開發平台,可以使用其中的Spy快速的找到視窗的類型,在Spy++中有一個FindWindow工具,它允許你使用滑鼠選擇視窗,然後Spy++會顯示這個視窗的類。
同時在微軟的協助文檔中也給出了對微軟常用OFFICE工具表單控制代碼尋找的方法,同樣是用FindWindow()方法,連結:http://support.microsoft.com/kb/302295/zh-cn
(4)尋找子表單的方法
[DllImport("user32.dll", EntryPoint = "FindWindow")]
private static extern IntPtr FindWindowEx( IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow )
其中hwndParent是要尋找子視窗的父視窗控制代碼,如果hwndParent為Null,則函數以桌面視窗為父視窗,尋找桌面視窗的所有子視窗;如果hwndParent是HWND_MESSAGE,函數僅尋找所有訊息視窗;
hwndChildAfter :子視窗控制代碼。尋找從在Z序中的下一個子視窗開始。子視窗必須為hwndPareRt視窗的直接子視窗而非後代視窗。如果HwndChildAfter為NULL,尋找從hwndParent的第一個子視窗開始。如果hwndParent 和 hwndChildAfter同時為NULL,則函數尋找所有的頂層視窗及訊息視窗。
lpszClass:指向一個指定了類名的空結束字串,或一個標識類名字串的成員的指標。如果該參數為一個成員,則它必須為前次調用theGlobaIAddAtom函數產生的全域成員。該成員為16位,必須位於lpClassName的低16位,高位必須為0。
lpszWindow:指向一個指定了視窗名(視窗標題)的空結束字串。如果該參數為 NULL,則為所有視窗全匹配。傳回值:如果函數成功,傳回值為具有指定類名和視窗名的視窗控制代碼。如果函數失敗,傳回值為NULL。總之,這個函數尋找子視窗,從排在給定的子視窗後面的下一個子視窗開始。在尋找時不區分大小寫。
下面通過一個簡單的例子來說明對子視窗的尋找。相信大家都有QQ號,那麼就寫一個簡單的外掛:通過尋找QQ登陸視窗並類比按鍵實現QQ的自動登陸,以下只是介紹其中如何通過父表單尋找子表單
const int BM_CLICK = 0xF5; IntPtr maindHwnd = FindWindow(null, "QQ使用者登入"); //獲得QQ登陸框的控制代碼 if (maindHwnd != IntPtr.Zero) { IntPtr childHwnd = FindWindowEx(maindHwnd, IntPtr.Zero, null, "登入"); //獲得按鈕的控制代碼 if (childHwnd != IntPtr.Zero) { SendMessage(childHwnd, BM_CLICK, 0, 0); //發送點擊按鈕的訊息 } else { MessageBox.Show("沒有找到子視窗"); } } else { MessageBox.Show("沒有找到視窗");
(5)找到表單後對其的簡單處理,比如開關,隱藏
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern int ShowWindow(IntPtr hwnd, int nCmdShow);
其中ShowWindow(IntPtr hwnd, int nCmdShow);
nCmdShow的含義
0 關閉視窗
1 正常大小顯示視窗
2 已最小化的視窗
3 已最大化的視窗
(6)擷取視窗大小及位置
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; //最左座標
public int Top; //最上座標
public int Right; //最右座標
public int Bottom; //最下座標
}
樣本:
InPtr awin = GetForegroundWindow(); //擷取當前視窗控制代碼
RECT rect = new RECT();
GetWindowRect(awin, ref rect);
int width = rc.Right - rc.Left; //視窗的寬度
int height = rc.Bottom - rc.Top; //視窗的高度
int x = rc.Left;
int y = rc.Top;
(7)常用操作:
GetClassName(
hWnd: HWND; {指定視窗控制代碼}
lpClassName: PChar; {緩衝區}
nMaxCount: Integer {緩衝區大小}
): Integer; {返回類名大小; 失敗返回 0}擷取指定視窗的類名
GetNextWindow(
hWnd: HWND; {指定的視窗控制代碼}
uCmd: UINT {指定的關係選項}
): HWND; {失敗返回0; 成功返回符合的視窗控制代碼}
//uCmd 可選值:
GW_HWNDNEXT = 2; {同層級 Z 序之下}
GW_HWNDPREV = 3; {同層級 Z 序之上}擷取指定視窗Z上或Z下的視窗的控制代碼
GetTopWindow(
hWnd: HWND; {指定的視窗控制代碼}
): HWND; {失敗返回0; 成功返回最頂層的子視窗控制代碼}擷取指定視窗的子視窗中最頂層的視窗控制代碼
GetWindow(
hWnd: HWND; {指定的視窗控制代碼}
uCmd: UINT {指定的關係選項}
): HWND; {失敗返回0; 成功返回符合的視窗控制代碼}
//uCmd 可選值:
GW_HWNDFIRST = 0; {同層級第一個}
GW_HWNDLAST = 1; {同層級最後一個}
GW_HWNDNEXT = 2; {同層級下一個}
GW_HWNDPREV = 3; {同層級上一個}
GW_OWNER = 4; {屬主視窗}
GW_CHILD = 5; {子視窗}擷取與指定視窗具有指定關係的視窗的控制代碼
GetWindowTextLength(
hWnd: HWND {視窗控制代碼}
): Integer; {返回視窗標題長度} 擷取視窗標題長度
SetWindowText(
hWnd: HWND; {視窗控制代碼}
lpString: PChar {新標題串指標}
): BOOL;設定視窗標題
GetDesktopWindow: HWND; {無參數; 返回桌面視窗的控制代碼}
(8)還有個問題尚待解決
前面我們提到找到目標控制代碼後通過SetForeGroudWindow(int hwnd)方法可以將其啟用並設定為前台視窗,但是如果只是想將其啟用而不設定為前台的話就要用到函數SetActiveWindow()
但是使用該方法要特別注意,當在其他線程中對當前線程表單進行啟用使用該方法是沒有作用的。具體解決以後再給出,先去吃飯去......