【向重複工作說不】c#之模擬滑鼠操作

keenrob發表於2020-12-07

一.寫在前面

作為一個人力資源工作者,會經常遇到填表、報表的事務,其實有時候就是重複再重複的點選滑鼠工作,特別是遇到一些複雜的客戶端程式、網頁程式,諸如用友客戶端、社保管理系統等等,就尤其讓人頭疼。正好這段時間做了很多這方面的工作,搜尋了不少的資料,為了轉化學習效果,記錄於此,溫故知新。

二.引用Windows API

c#模擬滑鼠操作,就必須和WindowsAPI打交道,通過引用它內部的幾個函式,從而實現在螢幕的指定位置單擊、雙擊,或者對指定的窗體(能夠獲得控制程式碼的)、控制元件進行相關控制操作。相關函式如下:

SetCursorPos(設定滑鼠位置) mouse_event(控制滑鼠動作)
FindWindow(獲得視窗的控制程式碼) FindWindowEx(獲得子視窗或控制元件的控制程式碼)

SetCursorPos設定滑鼠位置
//設定滑鼠位置
[DllImport("user32.dll")] //DllImpor針對非託管的。非託管指的是不利用.net 生成的DLL
//宣告一個外部實現方法SetCursorPos()
public static extern bool SetCursorPos(int X, int Y);

這裡定義宣告動態連結庫user32.dll作為靜態入口點。SetCursorPos是這個動態連結庫裡面的內部方法,所以這裡不要試圖改變大小寫什麼的。這裡定義的方法使用 extern 修飾符意味著該方法在 C# 程式碼外部實現。extern 修飾符的常見用法是在使用 Interop 服務調入非託管程式碼時與DllImport 特性一起使用。在這種情況下,還必須將方法宣告為static

mouse_event控制滑鼠動作
//控制滑鼠動作
[DllImport("user32.dll")]
public static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo); 

MouseEventFlag 繼承uint(uint型為無符號32位整數,佔4個位元組,取值範圍在0~4,294,967,295之間。)的列舉,指代一組滑鼠動作標誌位集;
dx指滑鼠沿x軸絕對位置或上次滑鼠事件位置產生以來移動的畫素數量;dy指沿y軸的絕對位置或從上次滑鼠事件以來移動的畫素數量;
data變數是指,如果flags為MOUSE_WHEEL,則該變數指定滑鼠輪移動的數量。正值表明滑鼠輪向前轉動,即遠離使用者的方向;負值表明滑鼠輪向後轉動,即朝向使用者。一個輪擊定義為WHEEL_DELTA,即120。如果flags不是MOUSE_WHEEL,則data應為零;
extraInfo指定與滑鼠事件相關的附加32位值,應用程式呼叫函式GetMessageExtraInfo來獲得此附加資訊。一般的情況下賦值IntPtr.ZeroUIntPtr用於表示指標或控制程式碼的特定型別(A platform-specific type that is used to represent a pointer or a handle.)它被設計成整數,其大小適用於特定平臺。它們主要用於本機資源,如視窗控制程式碼)

FindWindow獲得視窗的控制程式碼
//在視窗列表中尋找與指定條件相符的第一個視窗,並返回控制程式碼值。這個函式不能查詢子視窗。
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

lpClassName是視窗的類名, lpWindowName是視窗的標題。這兩個變數可以用Spy++軟體來獲得。
在搜尋的時候不一定兩者都知道,但至少要知道其中的一個(不知道的可以賦值null)。有的視窗的標題是比較容易得到的,如"計算器",所以搜尋時應使用標題進行搜尋。但有的軟體的標題不是固定的,如"記事本",如果開啟的檔案不同,視窗標題也不同,這時使用視窗類搜尋就比較方便。如果找到了滿足條件的視窗,這個函式返回該視窗的控制程式碼,否則返回0。如果查詢子視窗需要用FindWindowEx

FindWindowEx獲得視窗或者控制元件的控制程式碼
//該函式獲得一個視窗的控制程式碼,該視窗的類名和視窗名與給定的字串相匹配。這個函式查詢子視窗,從排在給定的子視窗後面的下一個子視窗開始。在查詢時不區分大小寫。
[DllImport("user32.dll", EntryPoint = "FindWindowEx", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

hwndParent,父視窗控制程式碼,如果hwndParent為 0 ,則函式以桌面視窗為父視窗,查詢桌面視窗的所有子視窗。
hwndChildAfter,子視窗控制程式碼,查詢從在Z序中的下一個子視窗開始。子視窗必須為hwndParent視窗的直接子視窗而非後代視窗。如果HwndChildAfter為NULL,查詢從hwndParent的第一個子視窗開始。如果hwndParent 和 hwndChildAfter同時為NULL,則函式查詢所有的頂層視窗及訊息視窗。
lpszClass ,視窗或控制元件類名;lpszWindow ,視窗或控制元件標題。如果該引數為 NULL,則為所有視窗全匹配。

三.應用

1 首先新建一個類-類名WindowApi

using System;
using System.Runtime.InteropServices;//需要引用,從而使相應的類或者方法來支援託管/非託管模組間的互相呼叫

namespace 模擬輸入
{
    public static class WindowApi
    {
        #region 滑鼠操作
        //首先定義一個列舉,其繼承uint。這樣可以直觀的體現滑鼠的各類動作。
        //[Flags]位標誌屬性,從而使該列舉型別的例項可以儲存列舉列表中定義值的任意組合。可以用 與(&)、或(|)、異或(^)進行相應的運算。
        [Flags]
        public 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")]
        public static extern bool SetCursorPos(int X, int Y);

        //設定滑鼠按鍵和動作
        [DllImport("user32.dll")]
        public static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo);
        
        //方法:滑鼠左鍵單擊操作:滑鼠左鍵按下和鬆開兩個事件的組合即一次單擊
        public static void MouseLeftClickEvent(int dx, int dy, uint data)
        {
            SetCursorPos(dx, dy);
            System.Threading.Thread.Sleep(2 * 1000);
            mouse_event(MouseEventFlag.LeftDown|MouseEventFlag.LeftUp, dx, dy, data, UIntPtr.Zero);
        }
         //方法:滑鼠右鍵單擊操作:滑鼠右鍵鍵按下和鬆開兩個事件的組合即一次單擊
        public static void MouseRightClickEvent(int dx, int dy, uint data)
        {
            SetCursorPos(dx, dy);
            System.Threading.Thread.Sleep(2 * 1000);
            mouse_event(MouseEventFlag.RightDown|MouseEventFlag.RigthtUp, dx, dy, data, UIntPtr.Zero);
        }

        #endregion

        #region 控制程式碼函式
        
        //獲得視窗的控制程式碼
        [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
        
        //獲得子視窗、子控制元件的控制程式碼;需要提前知道父窗體的控制程式碼,以及視窗的類名或者標題名。
        [DllImport("user32.dll", EntryPoint = "FindWindowEx", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

        //該函式返回指定視窗的邊框矩形的尺寸。該尺寸以相對於螢幕座標左上角的螢幕座標給出
        [DllImport("user32.dll")]
        public static extern bool GetWindowRect(IntPtr hwnd, out NativeRECT rect);
        
       


       #endregion  
    }
}

2 在WinForm中使用剛才新建的WindowApi

假如:有這麼一個程式,我們要進行這樣的操作“ 點選滑鼠到查詢文字框,輸入查詢關鍵字,點選查詢按鈕,獲得查詢的內容,然後在點選窗體上的列印按鈕,調出win系統的列印對話方塊(輸出PDF),輸入PDF的檔名,最後列印輸出。這樣的動作迴圈若干次。 ”因為無法獲得這個程式裡面查詢文字框控制元件的控制程式碼,我們就必須模擬滑鼠的操作,點中這個查詢框,然後用SendKeys.SendWait("待查詢關鍵字"),傳送給定的內容,然後再用滑鼠點選這個查詢按鈕。

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void BtnOutPut_Click(object sender, EventArgs e)
        {
            string str="查詢關鍵字";
            Thread.Sleep(1000);
            for (int i = 0; i < 10; i++)
            {
                str += i;
                Clipboard.SetText(str);

                WindowApi.MouseLeftClickEvent(800, 160, 0);//將滑鼠定位到文字框,假設文字框的中間位置的座標是800,160
                //由於SendKeys.SendWait不能傳送中文字元,所以只能用複製貼上的方式折中。
                SendKeys.SendWait("^A");//全選
                SendKeys.SendWait("^V");//將剪貼簿的內容貼上。
                
                Thread.Sleep(1000);
                WindowApi.MouseLeftClickEvent(950, 160, 0);//將滑鼠點選查詢按鈕
                WindowApi.MouseLeftClickEvent(1000, 300, 0);//將滑鼠點選列印按鈕
                //根據控制程式碼找到列印窗體
                IntPtr ptrTaskbar = WindowApi.FindWindow(null, "列印設定");
                if (ptrTaskbar != IntPtr.Zero)
                {
                    IntPtr ptrOKBtn = WindowApi.FindWindowEx(ptrTaskbar, IntPtr.Zero, "TButton", "確認");//找到列印窗體中的確定按鈕併傳送確認資訊。
                    WindowApi.GetWindowRect(ptrOKBtn, out WindowApi.NativeRECT rect);
                    this.textControlPos.Text = rect.bottom.ToString() + "--" + rect.left.ToString();
                    WindowApi.SetCursorPos(rect.left + 40, rect.bottom - 15);//將滑鼠定位到列印按鈕
                                                                             //Thread.Sleep(1000);//delay 5m
                    WindowApi.mouse_event(WindowApi.MouseEventFlag.LeftDown, 0, 0, 0, UIntPtr.Zero);//單擊滑鼠左鍵
                    WindowApi.mouse_event(WindowApi.MouseEventFlag.LeftUp, 0, 0, 0, UIntPtr.Zero);//單擊滑鼠左鍵
                    Thread.Sleep(5000);
                    //定位列印輸出窗體
                    IntPtr ptrPrintOutputForm = WindowApi.FindWindow(null, "將列印輸出另存為");
                    IntPtr ptrPrintOutputForm_Edit = WindowApi.FindWindowEx(ptrPrintOutputForm, "Edit", true);//。
                    this.textControlPos.Text = ptrPrintOutputForm_Edit.ToString() + "----" + ptrPrintOutputForm.ToString();
                    WindowApi.SendMessage(ptrPrintOutputForm_Edit, 0x000C, null,str);//輸入另存為的檔名
                    IntPtr ptrPrintOutputForm_Save = WindowApi.FindWindowEx(ptrPrintOutputForm, IntPtr.Zero, "Button", "儲存(&S)");
                    WindowApi.SendMessage(ptrPrintOutputForm_Save, 0xF5, 0, 0);//儲存
                }
                else
                {
                    MessageBox.Show("未能找到列印窗體");
                }

                str = "查詢關鍵字";
            }

            
        }

參考資料

C# 模擬滑鼠移動與點選
C# 系統應用之滑鼠模擬技術及自動操作滑鼠
C#應用WindowsApi實現查詢\列舉(FindWindow、EnumChildWindows)窗體控制元件,併傳送訊息。
c#裡FindWindow的用法
C#模擬滑鼠和鍵盤操作

相關文章