C# WPF MVVM QQ密碼管家專案(8,完結篇:自動輸入QQ號、密碼)
目錄:
8,完結篇:自動輸入QQ號、密碼
接上篇,獲取QQ登入介面控制代碼、視窗位置、滑鼠/鍵盤操作等都需要用到win32api
win32api是windows系統預留的介面,通過介面我們可以實現對系統更加深度地操作。
第1步:獲取視窗控制代碼
win32api獲取視窗控制代碼方法c#程式碼:
#region查詢視窗控制代碼 [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); #endregion
有兩個引數可以填,第一個lpClassName填類名,第二個lpWindowName填視窗名。也就是說可以通過類名或者視窗名獲得指定視窗的控制代碼(不填的引數應為:null),也可以兩個引數都填上(在某些情況下使用用於精準定位視窗)。這裡的話我們只填一個視窗名引數即可。
FindWindow(null, "QQ");
QQ視窗名通過spy++可以獲取。
重溫一下自動輸入的步驟:
1,獲取登入視窗控制代碼;
2,獲得視窗大小;
3,獲得視窗座標;
4,計算兩個輸入框的位置;
5,模擬滑鼠選中輸入框獲得輸入焦點;
6,模擬鍵盤輸入,將qq號、密碼輸入。
第1步已完成,QQ登入視窗控制代碼我們已經得到了。接下來是第2步,獲得視窗大小。
因為win32api中並沒有直接提供一個獲取視窗大小的方法,但提供了一個獲取視窗尺寸的方法。你可能有點暈,我們先看一下獲取視窗尺寸的win32api方法官方說明:
BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);
該函式返回指定視窗的邊框矩形的尺寸。該尺寸以相對於螢幕座標左上角的螢幕座標給出
該函式返回指定視窗的邊框矩形的尺寸,該尺寸是相對於螢幕左上角的,而不是直接給你一個寬是多高是多少的直接資料,所以我們需要稍作計算。
#region 獲得視窗位置 [DllImport("user32.dll")] private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect); public struct Rect { public int Left; public int Top; public int Right; public int Bottom; } #endregion
使用的時候填入兩個引數,第一個是視窗控制代碼,第二個是輸出的資料變數。那麼輸出的Rect結構資料就是視窗相對於螢幕的尺寸了。
int Left是視窗左邊框相對於螢幕最左邊的距離;
int Top是視窗頂部邊框相對於螢幕頂部的距離;
int Right是視窗右邊框相對於螢幕最左邊的距離;
int Bottom是視窗下邊框相對於螢幕頂部的距離;
假設B為螢幕,A是QQ登入介面,那麼GetWindowRect獲取到的相應資料如圖所示
將RIGHT減去LEFT得到的就是視窗的真實寬度,將BOTTOM-TOP得到的就是視窗的真實高度。到這裡第2個步驟“獲得視窗大小”就完成了。
我們整合成一個方法
#region 獲得視窗大小 public class Size { public int width { get; set; } public int height { get; set; } } public static Size GetWindowSize(IntPtr window) { Rect rect = new Rect(); GetWindowRect(window, out rect); return new Size() { width = rect.Right - rect.Left, height = rect.Bottom - rect.Top }; } #endregion
第3步獲取視窗座標其實在獲取視窗尺寸的時候已經完成了,RECT int Left就是視窗所在的X座標,int Top就是視窗的Y座標。又省了一步。
第4步,計算兩個輸入框的位置:
有句話在魔術表演中經常能聽到,眼睛會欺騙你。窗體也是,你所見的並不一定是真實的寬和高。試試開啟QQ截圖功能將滑鼠移動到QQ登入視窗上試試,會有一個預設的框選區域(框選出視窗的完整區域),得到的是這樣一個範圍截圖:
也就是說其實QQ登入視窗是多出很多透明區域的,如果你忽略了這些區域,將來計算是會出現偏差的,我是沒辦法負責任的哦。所以,我們在計算輸入框位置時要把透明的區域計算進去。(用QQ截圖的預設框選區域在某些情況下也不一定精準,請用我們剛才第2步寫的GetWindowSize方法獲取到寬高的資料來計算)
得到QQ登入視窗真實尺寸,可以將截圖放入PS中,或者用你喜歡的辦法量出幾個資料。如下圖:
上圖我畫出了3條線,A、B、C。要獲得QQ號碼輸入框的輸入焦點我們要讓程式模擬滑鼠點選一下輸入框的位置,也就是座標(A,B),也就是點選QQ號碼輸入框輸入區域的位置(只要座標在輸入框內即可,不一定是跟我畫的線座標一樣的);那麼密碼輸入框點選的座標就是(A,C),只有三條線是因為密碼輸入框的橫座標跟QQ號碼輸入框的橫座標是一樣的,所以直接用A就行。
QQ號碼輸入框應該點選的座標是:x=(A÷視窗寬度)×視窗寬度+LEFT(視窗距離螢幕左邊的距離),y=(B÷視窗高度)×視窗高度+TOP(視窗距離螢幕頂部的距離);
QQ密碼輸入框應該點選的座標是:x=(A÷視窗寬度)×視窗寬度+LEFT,y=(C÷視窗高度)×視窗高度+TOP;
視窗寬高和視窗距離螢幕的距離我們第2步已經給出了~
注意,這裡的A、B、C的距離都需要手動量的(可以用PS拉個線條量或者下載個螢幕直尺軟體什麼的用你喜歡的就行)。
程式碼(省略了上面的計算步驟):
#region 獲得qq號、密碼輸入框座標 public class Point { public int x { get; set; } public int y { get; set; } } public static Point GetQQNumberInputPoint(IntPtr window) { double A = 0.3656565656565657; double B = 0.5978723404255319; //獲得視窗寬度 Size window_size = GetWindowSize(window); //獲得視窗尺寸 Rect rect = new Rect(); GetWindowRect(window, out rect); //計算 int x = (int)(A * window_size.width + rect.Left); int y = (int)(B * window_size.height + rect.Top); return new Point() { x = x, y = y }; } public static Point GetQQPassInputPoint(IntPtr window) { double A = 0.3656565656565657; double C = 0.6617021276595745; //獲得視窗寬度 Size window_size = GetWindowSize(window); //獲得視窗尺寸 Rect rect = new Rect(); GetWindowRect(window, out rect); //計算 int x = (int)(A * window_size.width + rect.Left); int y = (int)(C * window_size.height + rect.Top); return new Point() { x = x, y = y }; } #endregion
接下來第5步,模擬滑鼠點選輸入框。win32api方法是:
#region 設定游標位置 [DllImport("user32.dll")] private static extern bool SetCursorPos(int X, int Y); #endregion
到第6步,非常關鍵的一步了。我們的qq密碼中可能會存在很多種組合大小寫字母+數字+符號,模擬鍵盤操作的時候需要注意大小寫字母的判斷和切換,還有某些符號是需要按住shift鍵才能按出來的,情況非常複雜。但是,沒有什麼是程式設計不能實現的。
首先看鍵盤操作的win32api方法:
#region 鍵盤操作 [DllImport("user32.dll", EntryPoint = "keybd_event")] public static extern void keybd_event( byte bVk, //虛擬鍵值 byte bScan,// 一般為0 int dwFlags, //這裡是整數型別 0 為按下,2為釋放 int dwExtraInfo //這裡是整數型別 一般情況下設成為 0 ); #endregion
這個虛擬鍵值可以通過搜尋引擎查到完整的,我這就不貼出來了,但是呢,我們不需要去查,為什麼呢?有win32api可以直接將字元轉為虛擬鍵值。
#region 將一個字元轉換為虛擬鍵值 [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern short VkKeyScan(char ch); #endregion
注意哦,是字元,不是字串,win32api沒有方便到自動幫我們轉換一整個字串的鍵碼。所以等會我們還需要稍加改進。
剛才說了有些符號需要按住shift鍵,還要區分大小寫,所以我們需要一些win32api去判斷和操作。
#region 大小寫、shift等鍵狀態獲取和設定 //大寫鎖定是否啟用 public static bool IsCapsLock = false; const uint KEYEVENTF_EXTENDEDKEY = 0x1; const uint KEYEVENTF_KEYUP = 0x2; [DllImport("user32.dll")] static extern short GetKeyState(int nVirtKey); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); public enum VirtualKeys : byte { VK_NUMLOCK = 0x90, //數字鎖定鍵 VK_SCROLL = 0x91, //滾動鎖定 VK_CAPITAL = 0x14, //大小寫鎖定 VK_A = 62 } public static bool GetState(VirtualKeys Key) { return (GetKeyState((int)Key) == 1); } public static void SetState(VirtualKeys Key, bool State) { if (State != GetState(Key)) { keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); } } //設定大小寫按鈕是否啟用(true啟用,false禁用) public static void SetCapLock(bool islock) { if (IsCapsLock != islock) { keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); IsCapsLock = islock; } } #endregion
整合上面的程式碼變成一個方法(註釋比較多也不知道哪些地方需要解釋,所以下面開始就不多說了直接貼程式碼,不懂的地方可以在評論提問吧):
#region 通過字串模擬鍵盤輸入 public static bool IsNeedShift(char c) { //需要按SHIFT的字元 char[] needlist = { '<', '>', '?', ':', '"', '|', '{', '}', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' }; if (needlist.Contains(c)) { return true; } else { return false; } } public static void SendKeyOfString(string str) { foreach (char c in str) { bool isshiftdown = false; //判斷字元是否是符號 if (char.IsPunctuation(c) || char.IsSymbol(c)) { //判斷符號是否需要按住shift if (IsNeedShift(c)) { //需要按shift //輸入鍵值 keybd_event((byte)16, 0, 0, 0); isshiftdown = true; } } //判斷字元是否是數字 if (char.IsNumber(c) == false) { //不是數字,判斷是否是大寫字母 if (char.IsUpper(c)) { //大寫字母 //開啟caps lock鎖定 SetCapLock(true); } else { //小寫字母 //關閉caps lock鎖定 SetCapLock(false); } } //模擬鍵盤按下 keybd_event((byte)VkKeyScan(c), 0, 0, 0); //模擬鍵盤釋放 keybd_event((byte)VkKeyScan(c), 0, 2, 0); if (isshiftdown) { //釋放shift keybd_event((byte)16, 0, 2, 0); isshiftdown = false; } } } #endregion
到此自動輸入的步驟程式碼我們都完成了,現在開始整合上面的程式碼完成我們的專案。
在專案根目錄新建一個資料夾:Cores(核心程式碼),在cores資料夾新建一個類:Win32API.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace qqkeys.Cores { public class Win32API { //win32api #region win32api #region 查詢視窗控制代碼 [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); #endregion #region 設定游標位置 [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y); #endregion #region 滑鼠操作 //移動滑鼠 public const int MOUSEEVENTF_MOVE = 0x0001; //模擬滑鼠左鍵按下 public const int MOUSEEVENTF_LEFTDOWN = 0x0002; //模擬滑鼠左鍵擡起 public const int MOUSEEVENTF_LEFTUP = 0x0004; //模擬滑鼠右鍵按下 public const int MOUSEEVENTF_RIGHTDOWN = 0x0008; //模擬滑鼠右鍵擡起 public const int MOUSEEVENTF_RIGHTUP = 0x0010; //模擬滑鼠中鍵按下 public const int MOUSEEVENTF_MIDDLEDOWN = 0x0020; //模擬滑鼠中鍵擡起 public const int MOUSEEVENTF_MIDDLEUP = 0x0040; //標示是否採用絕對座標 public const int MOUSEEVENTF_ABSOLUTE = 0x8000; [DllImport("user32.dll")] private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); #endregion #region 鍵盤操作 [DllImport("user32.dll", EntryPoint = "keybd_event")] public static extern void keybd_event( byte bVk, //虛擬鍵值 byte bScan,// 一般為0 int dwFlags, //這裡是整數型別 0 為按下,2為釋放 int dwExtraInfo //這裡是整數型別 一般情況下設成為 0 ); #endregion #region 獲得視窗位置 [DllImport("user32.dll")] private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect); public struct Rect { public int Left; public int Top; public int Right; public int Bottom; } #endregion #region 大小寫、shift等鍵狀態獲取和設定 //大寫鎖定是否啟用 public static bool IsCapsLock = false; const uint KEYEVENTF_EXTENDEDKEY = 0x1; const uint KEYEVENTF_KEYUP = 0x2; [DllImport("user32.dll")] static extern short GetKeyState(int nVirtKey); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo); public enum VirtualKeys : byte { VK_NUMLOCK = 0x90, //數字鎖定鍵 VK_SCROLL = 0x91, //滾動鎖定 VK_CAPITAL = 0x14, //大小寫鎖定 VK_A = 62 } public static bool GetState(VirtualKeys Key) { return (GetKeyState((int)Key) == 1); } public static void SetState(VirtualKeys Key, bool State) { if (State != GetState(Key)) { keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); } } //設定大小寫按鈕是否啟用(true啟用,false禁用) public static void SetCapLock(bool islock) { if (IsCapsLock != islock) { keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0); keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); IsCapsLock = islock; } } #endregion #region 將一個字元轉換為鍵碼 [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern short VkKeyScan(char ch); #endregion #endregion //擴充套件 #region 擴充套件 #region 滑鼠單擊 public static void MouserClick() { mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //模擬滑鼠按下操作 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); //模擬滑鼠放開操作 } #endregion #region 獲得視窗大小 public class Size { public int width { get; set; } public int height { get; set; } } public static Size GetWindowSize(IntPtr window) { Rect rect = new Rect(); GetWindowRect(window, out rect); return new Size() { width = rect.Right - rect.Left, height = rect.Bottom - rect.Top }; } #endregion #region 獲得qq號、密碼輸入框座標 public class Point { public int x { get; set; } public int y { get; set; } } public static Point GetQQNumberInputPoint(IntPtr window) { double A = 0.3656565656565657; double B = 0.5978723404255319; //獲得視窗寬度 Size window_size = GetWindowSize(window); //獲得視窗尺寸 Rect rect = new Rect(); GetWindowRect(window, out rect); //計算 int x = (int)(A * window_size.width + rect.Left); int y = (int)(B * window_size.height + rect.Top); return new Point() { x = x, y = y }; } public static Point GetQQPassInputPoint(IntPtr window) { double A = 0.3656565656565657; double C = 0.6617021276595745; //獲得視窗寬度 Size window_size = GetWindowSize(window); //獲得視窗尺寸 Rect rect = new Rect(); GetWindowRect(window, out rect); //計算 int x = (int)(A * window_size.width + rect.Left); int y = (int)(C * window_size.height + rect.Top); return new Point() { x = x, y = y }; } #endregion #region 通過字串模擬鍵盤輸入 //判斷字元是否需要按住shift public static bool IsNeedShift(char c) { //需要按SHIFT的字元 char[] needlist = { '<', '>', '?', ':', '"', '|', '{', '}', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' }; if (needlist.Contains(c)) { return true; } else { return false; } } //模擬鍵盤操作輸入指定字串,不支援中文!!!!!! public static void SendKeyOfString(string str) { foreach (char c in str) { bool isshiftdown = false; //判斷字元是否是符號 if (char.IsPunctuation(c) || char.IsSymbol(c)) { //判斷符號是否需要按住shift if (IsNeedShift(c)) { //需要按shift //輸入鍵值 keybd_event((byte)16, 0, 0, 0); isshiftdown = true; } } //判斷字元是否是數字 if (char.IsNumber(c) == false) { //不是數字,判斷是否是大寫字母 if (char.IsUpper(c)) { //大寫字母 //開啟caps lock鎖定 SetCapLock(true); } else { //小寫字母 //關閉caps lock鎖定 SetCapLock(false); } } //模擬鍵盤按下 keybd_event((byte)VkKeyScan(c), 0, 0, 0); //模擬鍵盤釋放 keybd_event((byte)VkKeyScan(c), 0, 2, 0); if (isshiftdown) { //釋放shift keybd_event((byte)16, 0, 2, 0); isshiftdown = false; } } } #endregion #endregion } }
在cores資料夾下新建一個類:QQAutoInput.cs
using qqkeys.Models; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using static qqkeys.Cores.Win32API; namespace qqkeys.Cores { public class QQAutoInput { public static void Start(QQModel qm) { //需要獲取大小寫鎖定狀態 IsCapsLock =Win32API.GetState(VirtualKeys.VK_CAPITAL); //獲取QQ視窗控制代碼 IntPtr qqptr = Win32API.FindWindow(null, "QQ"); //獲得qq號碼輸入框座標 Point qqnumberinputpoint = Win32API.GetQQNumberInputPoint(qqptr); //獲得qq密碼輸入框座標 Point qqpassinputpoint = Win32API.GetQQPassInputPoint(qqptr); //將滑鼠指標移動到QQ號輸入框位置 Win32API.SetCursorPos(qqnumberinputpoint.x, qqnumberinputpoint.y); //雙擊滑鼠 MouserClick(); MouserClick(); //滑鼠點選後需要加延遲,不然反應跟不上導致漏輸入或者不輸入 Thread.Sleep(100); SendKeyOfString(qm.qq.ToString()); Thread.Sleep(100); SetCursorPos(qqpassinputpoint.x, qqpassinputpoint.y); MouserClick(); MouserClick(); Thread.Sleep(100); SendKeyOfString(qm.password); } } }
在KeysViewModel.cs中新建一個命令:Command_Input
//命令 public Command Command_Input { get; set; } public KeysViewModel() { ............................ Command_Input = new Command(new Action<object>(Command_Input_Do)); } private void Command_Input_Do(object obj) { QQAutoInput.Start(SelectQQ); }
最後開啟KeysWindow.xaml,修改設計程式碼的“輸入”按鈕:
<Button Command="{Binding Command_Input}" Width="100" Height="25" Content="輸入" Margin="0,10,0,0"></Button>
到這裡這個專案已經完成
執行程式,看下效果~