寫在前面
最近公司沒有什么項目,想通過項目練練手的機會也沒有,只能自己學習了,因此空下來的時間也挺多的,就打開網頁看看吧,哎,一打開就讓簽到(像什么百度知道啊、百度雲盤啊之類的),我簽到的目的是獲取積分,便於下載資料^_^,真是煩的很,要是有個工具能幫助我全自動處理該有多好,想着想着大概的思路就出來了,無非就是開啟進程,傳入參數,確定坐標,點擊。OK啦啊哈哈~ 起來碼磚了……
好了,不閑聊了,干活,先上效果圖。(我對win7的毛玻璃界面情有獨鍾……^_^)

功能概覽
總體來說,這款小工具就是打開IE瀏覽器,輸入地址,然后通過API給鼠標定位,在模擬鼠標點擊的過程。但是在寫代碼的過程中,發現其實它不僅僅可以打開瀏覽器,還可以打開各種Windows下的工具,但是相對於瀏覽器的簽到功能,還是比較麻煩的,因為打開Windows下的工具后需要的操作比較復雜,而這個工具當初的定位也僅僅是簽到(點擊一下)。此外,這個小工具還可以添加任務、批量執行任務,添加任務時,首先把中間的三個文本框填好,也就是任務名稱、進程名稱、進程參數,詳細的內容參考上面的截圖,除了這些參數,還需要確定鼠標點擊的位置,在指定的位置點擊鼠標右鍵,就可以將任務中需要點擊的位置存儲起來,每點擊一次,該坐標都會更新一次。所有的參數都滿足了之后,最后單擊“新增任務”,那么一個任務就建立好了,以后就可以通過簽到器來幫你實現了~~。另外,下面有兩個線程休眠時間,一個是任務與任務之間的,另一個是單個任務之內的。在執行任務的過程中,需要將指定的線程Sleep,否則程序執行太快,瀏覽器還沒有打開,程序就結束了,豈不是很悲催~~~~所以這就需要設置線程內的休眠時間。那么線程間的休眠呢,其實這個倒無所謂了,怎么設置都可以,因為它不會影響任務的執行,不過為了不把我的簽到器累壞,還是設置了2秒鍾~~~~~~~哈哈。另外,任務的存儲我是采用XML格式的文件進行存儲,因為數據量不是很大,讀寫操作也比較方便。這里需要注意的是一般的網站簽到前提是需要登錄,所以必須在瀏覽器上有自己登錄信息的緩存才可以哦~
功能詳解
首先,簽到器中用到了全局的鼠標鈎子,運用鼠標鈎子的目的是可以使得鼠標在脫離Winform窗體后,仍然能夠捕獲鼠標的消息,如鼠標的移動、按鍵的按下等,當移動鼠標的時候,最上方的X、Y坐標是實時變化的,當單擊鼠標右鍵時,可以記錄單擊時的坐標信息,作為新建任務的一部分信息。這一塊用到了WinAPI的函數SetWindowsHookEx,具體代碼如下:
1 //裝置鈎子的函數 2 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 3 public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId); 4 5 //卸下鈎子的函數 6 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 7 public static extern bool UnhookWindowsHookEx(int idHook); 8 9 //下一個鈎掛的函數 10 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 11 public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); 12 13 //聲明委托 14 public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
安裝鈎子,代碼如下:
1 public void Start() 2 { 3 //安裝鼠標鈎子 4 if (hMouseHook == 0) 5 { 6 //生成一個HookProc的實例. 7 MouseHookProcedure = new HookProc(MouseHookProc); 8 9 //hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0); 10 hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName), 0); 11 12 //如果裝置失敗停止鈎子 13 if (hMouseHook == 0) 14 { 15 Stop(); 16 throw new Exception("SetWindowsHookEx failed."); 17 } 18 } 19 }
卸載鈎子,代碼如下:
1 public void Stop() 2 { 3 bool retMouse = true; 4 if (hMouseHook != 0) 5 { 6 retMouse = UnhookWindowsHookEx(hMouseHook); 7 hMouseHook = 0; 8 } 9 //如果卸下鈎子失敗 10 if (!(retMouse)) throw new Exception("UnhookWindowsHookEx failed."); 11 }
最重要的一步,監聽鼠標消息,代碼如下:
1 private int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam) 2 { 3 //如果正常運行並且用戶要監聽鼠標的消息 4 if ((nCode >= 0) && (OnMouseActivity != null)) { 5 MouseButtons button = MouseButtons.None; 6 int clickCount = 0; 7 8 switch (wParam) { 9 case WM_LBUTTONDOWN: 10 button = MouseButtons.Left; 11 clickCount = 1; 12 break; 13 case WM_LBUTTONUP: 14 button = MouseButtons.Left; 15 clickCount = 2; 16 break; 17 case WM_LBUTTONDBLCLK: 18 button = MouseButtons.Left; 19 clickCount = 3; 20 break; 21 case WM_RBUTTONDOWN: 22 button = MouseButtons.Right; 23 clickCount = 4; 24 break; 25 case WM_RBUTTONUP: 26 button = MouseButtons.Right; 27 clickCount = 5; 28 break; 29 case WM_RBUTTONDBLCLK: 30 button = MouseButtons.Right; 31 clickCount = 6; 32 break; 33 } 34 35 //從回調函數中得到鼠標的信息 36 MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct)); 37 MouseEventArgs e = new MouseEventArgs(button, clickCount, MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y, 0); 38 //if(e.X>700)return 1;//如果想要限制鼠標在屏幕中的移動區域可以在此處設置 39 OnMouseActivity(this, e); 40 } 41 return CallNextHookEx(hMouseHook, nCode, wParam, lParam); 42 }
最后在MainForm中,實例化並綁定委托方法就可以了。代碼如下:
1 public MainFrom() 2 { 3 InitializeComponent(); 4 mouse = new MouseHookEvents(); 5 mouse.OnMouseActivity += new MouseEventHandler(mouse_OnMouseActivity); 6 mouse.Start(); 7 task = new Task(); 8 } 9 10 11 private void mouse_OnMouseActivity(object sender, MouseEventArgs e) 12 { 13 txt_X.Text = e.X.ToString(); 14 txt_Y.Text = e.Y.ToString(); 15 //使用鼠標右鍵對當前坐標進行存儲 16 if (e.Button == MouseButtons.Right) 17 { 18 SavePoint(); 19 lblMsg.Text = string.Format("當前坐標(X,Y)=({0},{1})已存儲", txt_X.Text, txt_Y.Text); 20 } 21 }
OK,到這里鼠標消息的捕獲工作就完成了。下面說說數據的存儲,如下XML代碼就是存儲任務數據的結構:
1 <tasks> 2 <task> 3 <taskName>百度雲盤</taskName> 4 <application>iexplore.exe</application> 5 <param>http://www.baiduyun.me/forum.php</param> 6 <position> 7 <x>1244</x> 8 <y>140</y> 9 </position> 10 </task> 11 </tasks>
然后建立Task實體,包括對Task的增加、修改和執行等操作,這里比較簡單,無非就是對XML節點的操作。
1 public static List<Task> GetXmlTaskList() 2 { 3 doc.Load(path);//注意Load數據 4 XmlNodeList list = doc.SelectNodes("tasks/task"); 5 List<Task> tasks = new List<Task>(); 6 foreach (XmlElement item in list) 7 { 8 Task task = new Task(); 9 task.Name = item.SelectSingleNode("./taskName").InnerText; 10 task.Application = item.SelectSingleNode("./application").InnerText; 11 task.Url = item.SelectSingleNode("./param").InnerText; 12 task.PositionX = item.SelectSingleNode("./position/x").InnerText.ToInt(); 13 task.PositionY = item.SelectSingleNode("./position/y").InnerText.ToInt(); 14 tasks.Add(task); 15 } 16 return tasks; 17 }
然后在MainForm中取出任務,依次執行。對了,前面的增加任務無非就是執行任務的反向操作,向XML中添加節點。
還有一個問題,當執行任務時,如果執行任務的線程在UI主線程中時,簽到器的界面會出現假死的情況,這時候就需要采用多線程來進行處理,避免主線程的休眠導致的假死,代碼如下:
1 //執行任務列表 2 private void button2_Click(object sender, EventArgs e) 3 { 4 //ExcuteTask() 5 List<Task> tasks = TaskModel.GetTaskList(); 6 new Thread(() => 7 {//開啟新線程,避免與主線程UI沖突,導致界面假死 8 foreach (Task item in tasks) 9 { 10 ExcuteTask(item); 11 Thread.Sleep((int)numericUpDown1.Value*BASENUM); 12 } 13 }).Start(); 14 }
開啟進程使用Process類來完成,代碼如下:
1 public void ExcuteTask(Task task) 2 { 3 ProcessStartInfo ps = new ProcessStartInfo(task.Application, task.Url); 4 Process.Start(ps); 5 Thread.Sleep((int)numericUpDown2.Value * BASENUM); 6 //設置鼠標位置 7 MouseEvents.SetCursorPosition(task.PositionX, task.PositionY); 8 //模擬鼠標單擊 9 MouseEvents.MouseClick(); 10 }
至此,簽到工具基本的功能就實現了,原來懶人不是這么好做的呀啊哈哈,這就是為一個“懶”字付出的代價~~~~
總結
通過制作這個小玩意兒,也鞏固和擴展了自己的知識,寫代碼的過程中也遇到了一些問題,但是都被一個一個地解決掉了,只有這樣,自己的印象才會加深,下一次才不會在同一塊石頭上絆倒。比如,前面遇到了一個棘手的問題,關於鈎子的卸載,每次關閉程序,都會提示鈎子卸載失敗!最后發現,卸載鈎子寫在了創建鈎子的類的析構函數中,把卸載的函數放在Form_CLosed事件中就OK了。這只是一個初級的版本,我還設想了一些新的功能,如果能實現Task的Step定制就好了,就可以突破每次只能單擊一次的局限了。還有,不僅僅是執行IE下的任務,結合多步定制還可以執行其他應用程序,那這個小玩意兒的功能就豐富了。各位大神覺得有改進的意見或建議盡管提出,小弟感激不盡~~如果覺得好玩兒就給個贊吧~\(≧▽≦)/~
補充點兒
下班把項目帶回家,我發現出現Visual Studio無可用源,不可用源的錯誤,就是不能進入調試。原來是自己的IDE設置問題,這里提供個辦法,進入工具->選項->調試->常規->去掉“要求源文件與原始版本完全匹配”復選框就OK啦~~
另外,下一個版本准備添加一些新功能進去,那就不單單是個簡單的簽到器了,比如:
(1)任務按照進程進行分類:用IE就是執行IE的任務,與其他(如資源管理器等)無關
(2)新增其他的進程任務:如上面提到的資源管理器,我們日常工作中常用的文件夾就那么幾個,但每次都要雙擊,雙擊,再雙擊才能進入目標文件夾,簡直是浪費時間,有了這個東東,就可以把最常用的目錄放在眼前,當然你喜歡創建快捷方式也無所謂,蘿卜青菜,各有所愛,我強調的只是集中管理,把懶人精神發揚到底~
(3)每執行完一個任務關閉任務窗口:目前的功能比較粗糙,一兩個任務還好,一旦加到十個八個就會發現,窗口是一個接一個地往外躥,簡直受不了啊 ...
(4)對簽到性質的任務進行”已簽到“過濾:如果當天的簽到任務完成了,那么再次運行時,直接忽略已簽到的任務,直接去執行因各種原因而沒執行簽到的任務
嗯,暫時就這些吧,謝謝大家,不早了,洗洗睡了 ... ...
補充GitHub地址:請點我
作者:悠揚的牧笛
博客地址:http://www.cnblogs.com/xhb-bky-blog/p/4112441.html
聲明:本博客原創文字只代表本人工作中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關系。非商業,未授權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
