游戲程序的操作不外乎兩種——鍵盤輸入控制和鼠標輸入控制,幾乎所有游戲中都使用鼠標來改變角色的位置和方向,本文主要是講述如何使用C#調用Windows API函數實現鼠標模擬操作的功能.首先通過結合FindWindow和FindWindowEx尋找到窗體的按鈕,在通過SetCursorPos或mouse_event函數操作鼠標,同時涉及到通過spy++工具獲取窗體消息的信息.
.NET沒有提供改變鼠標指針位置、模擬單機操作的函數,但是可以通過調用Windows API函數實現.
[DllImport("user32.dll")]
static extern bool SetCursorPos(int X,int Y);
該函數用於設置鼠標的位置,其中X和Y是相對於屏幕左上角的絕對位置.
[DllImport("user32.dll")]
static extern void mouse_event(MouseEventFlag flags,int dx,int dy,uint data,UIntPtr extraInfo);
該函數不僅可以設置鼠標指針絕對位置,而且可以以相對坐標來設置位置.
其中flags標志位集,指定點擊按鈕和鼠標動作的多種情況.dx指鼠標沿x軸絕對位置或上次鼠標事件位置產生以來移動的數量.dy指沿y軸的絕對位置或從上次鼠標事件以來移動的數量.data如果flags為MOUSE_WHEEL則該值指鼠標輪移動的數量(否則為0),正值向前轉動.extraInfo指定與鼠標事件相關的附加32位值.
[DllImport("user32.dll")]
static extern IntPtr FindWindow(string strClass, string strWindow);
該函數根據類名和窗口名來得到窗口句柄,但是這個函數不能查找子窗口,也不區分大小寫.如果要從一個窗口的子窗口查找需要使用FIndWindowEX函數.
[DllImport("user32.dll")]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
string strClass, string strWindow);
該函數獲取一個窗口的句柄,該窗口的類名和窗口名與給定的字符串相匹配,該函數查找子窗口時從排在給定的子窗口後面的下一個子窗口開始.其中參數
hwnParent為要查找子窗口的父窗口句柄,若該值為NULL則函數以桌面窗口為父窗口,查找桌面窗口的所有子窗口.
hwndChildAfter子窗口句柄,查找從在Z序中的下一個子窗口開始,子窗口必須為hwnParent直接子窗口而非後代窗口,若hwnChildAfter為NULL,查找從父窗口的第一個子窗口開始.
strClass指向一個指定類名的空結束字符串或一個標識類名字符串的成員的指針.
strWindow指向一個指定窗口名(窗口標題)的空結束字符串.若為NULL則所有窗體全匹配.
返回值:如果函數成功,返回值為具有指定類名和窗口名的窗口句柄,如果函數失敗,返回值為NULL.
首先創建一個C#工程,設計的窗體如下圖所示,同時添加Timer時間器控件:
然後添加的如下代碼,即可實現鼠標模擬技術及自動操作鼠標:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; //引用新命名空間 using System.Runtime.InteropServices; //StructLayout namespace MouseAction { public partial class Form1 : Form { public Form1() { InitializeComponent(); } //結構體布局 本機位置 [StructLayout(LayoutKind.Sequential)] struct NativeRECT { public int left; public int top; public int right; public int bottom; } //將枚舉作為位域處理 [Flags] enum MouseEventFlag : uint //設置鼠標動作的鍵值 { Move = 0x0001, //發生移動 LeftDown = 0x0002, //鼠標按下左鍵 LeftUp = 0x0004, //鼠標松開左鍵 RightDown = 0x0008, //鼠標按下右鍵 RightUp = 0x0010, //鼠標松開右鍵 MiddleDown = 0x0020, //鼠標按下中鍵 MiddleUp = 0x0040, //鼠標松開中鍵 XDown = 0x0080, XUp = 0x0100, Wheel = 0x0800, //鼠標輪被移動 VirtualDesk = 0x4000, //虛擬桌面 Absolute = 0x8000 } //設置鼠標位置 [DllImport("user32.dll")] static extern bool SetCursorPos(int X, int Y); //設置鼠標按鍵和動作 [DllImport("user32.dll")] static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo); //UIntPtr指針多句柄類型 [DllImport("user32.dll")] static extern IntPtr FindWindow(string strClass, string strWindow); //該函數獲取一個窗口句柄,該窗口雷鳴和窗口名與給定字符串匹配 hwnParent=Null從桌面窗口查找 [DllImport("user32.dll")] static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string strClass, string strWindow); [DllImport("user32.dll")] static extern bool GetWindowRect(HandleRef hwnd, out NativeRECT rect); //定義變量 const int AnimationCount = 80; private Point endPosition; private int count; private void button1_Click(object sender, EventArgs e) { NativeRECT rect; //獲取主窗體句柄 IntPtr ptrTaskbar = FindWindow("WindowsForms10.Window.8.app.0.2bf8098_r11_ad1", null); if (ptrTaskbar == IntPtr.Zero) { MessageBox.Show("No windows found!"); return; } //獲取窗體中"button1"按鈕 IntPtr ptrStartBtn = FindWindowEx(ptrTaskbar, IntPtr.Zero, null, "button1"); if (ptrStartBtn == IntPtr.Zero) { MessageBox.Show("No button found!"); return; } //獲取窗體大小 GetWindowRect(new HandleRef(this, ptrStartBtn), out rect); endPosition.X = (rect.left + rect.right) / 2; endPosition.Y = (rect.top + rect.bottom) / 2; //判斷點擊按鈕 if (checkBox1.Checked) { //選擇"查看鼠標運行的軌跡" this.count = AnimationCount; movementTimer.Start(); } else { SetCursorPos(endPosition.X, endPosition.Y); mouse_event(MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero); mouse_event(MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero); textBox1.Text = String.Format("{0},{1}", MousePosition.X, MousePosition.Y); } } //Tick:定時器,每當經過多少時間發生函數 private void movementTimer_Tick(object sender, EventArgs e) { int stepx = (endPosition.X - MousePosition.X) / count; int stepy = (endPosition.Y - MousePosition.Y) / count; count--; if (count == 0) { movementTimer.Stop(); mouse_event(MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero); mouse_event(MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero); } textBox1.Text = String.Format("{0},{1}", MousePosition.X, MousePosition.Y); mouse_event(MouseEventFlag.Move, stepx, stepy, 0, UIntPtr.Zero); } } }
同時自定義一個對話框,增加一個button按鈕,其運行結果如下圖所示:
可以看到當運行程序勾選"查看鼠標運行的軌跡"並點擊"開始"按鈕後,會通過FindWindow和FindWindowEx函數查找窗體"Form1"的"button1"按鈕,並通過mouse_event移動鼠標和點擊鼠標.其中函數原型為:
IntPtr FindWindowEx( IntPtr hwndParent, // handle to parent window [父窗體句柄] IntPtr hwndChildAfter, // handle to child window [子窗體句柄] string strClass, // class name [窗體類名] string strWindow // window name [窗體名] );
但是怎樣找到窗體類名和按鈕的類名呢?由於初學,很多窗體我都沒有實現如QQ,它需要用到一個叫spy++的工具.
PS:第一次制作gif格式動態圖片,參照博客 http://blog.csdn.net/tangcheng_ok/article/details/8246792
如果修改代碼為:
//獲取任務欄句柄 IntPtr ptrTaskbar = FindWindow("Shell_TrayWnd",null); //托盤通知句柄 IntPtr ptrStartBtn = FindWindowEx(ptrTaskbar, IntPtr.Zero, "TrayNotifyWnd", null);
可以獲取電腦底部任務欄的托盤通知句柄,其中通過Spy++工具(VS中"工具"中自帶)查找如下圖所示:
同樣,我通過spy++工具獲取txt句柄,首先打開spy++工具,同時點擊"查找窗口"按鈕(望遠鏡),再點擊"查找程序工具"中按鈕拖拽至要查看的窗體中,點擊"確定"按鈕.
這樣就會顯示這個txt的信息,同時可以右擊"屬性"顯示窗體的類名、窗體題目、句柄等信息.
最後通過下面代碼可以獲取hello.txt的句柄:
//獲取記事本句柄 IntPtr ptrTaskbar = FindWindow("Notepad", null); IntPtr ptrStartBtn = FindWindowEx(ptrTaskbar, IntPtr.Zero, "Edit", null);
再通過mouse_event操作鼠標,同時可以通過SendMessage將指定的消息發送到一個或多個窗口,PostMessage將一個消息寄送到一個線程的消息隊列後就立即返回.實現消息傳遞等功能,學習ing~
該篇文章主要講述C#如何操作鼠標的事件,在制作游戲外掛或自動運行程序時非常實用,但遺憾的是在上面通過窗體名稱"Form1"獲取窗體時總是失敗,需要通過spy++獲取它的類名來實現.Why?同時如果想學習鍵盤模擬技術的可以研究SetWindowsHookEx(安裝鉤子)、CallNextHookEx(下一個鉤子)、UnhookWindowsHookEx(卸載鉤子)和鼠標Hook實現很多技術.
希望文章對大家有所幫助,如果有錯誤或不足之處,請見諒~
(By:Eastmount 2014年10月13日 晚上8點
http://blog.csdn.net/eastmount/)
參考資料-在線筆記:
本文主要參考書籍《C#網絡變成高級篇之網頁游戲輔助程序設計》張慧斌 王小峰著
1.C#獲取QQ聊天輸入框中內容 http://www.csharpwin.com/csharpspace/9133r5654.shtml
2.C#查找窗口,FindWindow用法(By-LYBwwp)http://blog.csdn.net/lybwwp/article/details/8168553
3.FindWindowEx用法(By-coolszy)
http://blog.csdn.net/coolszy/article/details/5523784
4.C# 隱藏任務欄開始按鈕關閉shell(By-sshhbb)http://blog.csdn.net/sshhbb/article/details/6605976
5.任務欄句柄 http://blog.csdn.net/wangjieest/article/details/6943241
6.C#如何在外部程序的密碼框內自動輸入密碼
http://biancheng.dnbcw.info/c/117849.html
7.C#實現對外部程序的調用操作 http://www.blue1000.com/bkhtml/c17/2012-11/70993.htm
8.百度知道 C#
API函數FindWindowEx返回子窗體的值為零
9.百度知道 用C#操作API實現填寫桌面窗體內的textbox並點擊窗體按鈕