紀錄一些 C# .Net 裡面的 WebBrowser 小用法
HtmlElement .GetAttribute() .SetAttribute() .InvokeMember()
IHTMLDocument2 .parentWindow.execScript()
WebBrowser.Document.Window.Frames
2018/01/15 補充
使用 NotifyIcon 達到最小化到系統列
利用 System.Threading.Mutex 做
唯一開啟,不能重複開啟程式
因為公司換新的人資系統
中午午休需要線上打卡,而我動不動就忘記去用
所以想做個自動打卡程式
本來想使用 WatiN 偷懶的去使用
結果發現官網已經消失,且NuGet找到的版本是2011年的
不知為何 在Windows 7 + Visual Studio 2015 的環境下居然無法運行
所以就放棄使用。觀察了新的人資系統發現是使用ASP.net 開發
所以第一時間就放棄使用 HttpWebRequest和HttpWebResponse去截取資料
(或許是因為經驗不夠或是相關知識不足,要模擬出ASP.net的 VIEWSTATE 太花時間)
所以決定直接使用 WebBrowser 去做個簡單打卡程式
簡單紀錄下一些事項,此程式只是自己再用的簡單程式,所以不是做得很嚴謹
一開始在運行程式的時候會跳出 "物件沒有支援這個屬性或方法'attachEvent'" 的錯誤(在網頁資訊)
Google之後發現是WebBrowser預設的IE運作版本太低,所以裡面的一些語法轉換會出問題
參考了Google到的前輩文章做了修改
[C#]設定WebBrowser Control運行的User Agent版本
做了機碼的修改,因為要修改機馬,所以需要使用管理者權限才能運行程式
using Microsoft.Win32; // 要修改機碼使用
using mshtml; // 後面需要用IHTMLDocument2
var appName = Process.GetCurrentProcess().MainModule.ModuleName; Registry.SetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION", appName, 11001, RegistryValueKind.DWord);
這樣之後就可以開始抓網頁內的資料了
HtmlElement userName = webBrowser.Document.GetElementById("txtAccount"); userName?.SetAttribute("value", IDText); HtmlElement userPass = webBrowser.Document.GetElementById("txtPwd"); userPass?.SetAttribute("value", PWText);
利用網頁的id資料抓出 HtmlElement 之後因為密碼關係 網頁不使用innerText所以會造成登入錯誤
所以改用 .SetAttribute("value", 值) 將密碼設進去 value
HtmlElement submit = webBrowser.Document.All["btnSubmit"]; submit?.InvokeMember("Click");
找出登入的按鈕後使用.InvokeMember("click") 送出登入功能
結果這時候進去要抓登入後的資料一直抓不到,後來才發現
原來登入後的頁面使用了IFrame,所以內框的資料一直抓不到
為了方便我是直接在新增一個 webBrowser 去連接 Frame的網址
<我沒特別驗證,我猜想在同一個程式裡面的WebBrowser,因為網域的關係
所以Cookie是共用的,另外如果網頁自己用到Session也是共用,所以很多資訊不用額外處理
這就是用 WebBrowser 元件的好處 "開發快速" 一堆細節可以省略處理 >
之後在 WebBrowser 的 DocumentCompleted 事件裡,判斷是否有 IFrame
if (webBrowser.Document.Window.Frames.Count > 0) { string url = webBrowser.Document.Window.Frames[0].Url.AbsoluteUri; wbFrames.Navigate(url); wbFrames.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted); }
之後因為 打卡的時候會跳出 確認框,所以需要在網頁讀取完成後
注入一個javascript去做自動確認功能
IHTMLDocument2 相關說明資料請自行Google
var temp = (IHTMLDocument2)wbFrames.Document.DomDocument; // JavaScript 自動確認 temp.parentWindow.execScript("function confirm(s){return 1;} ", "javaScript"); temp.parentWindow.execScript("function alert(s){return true;} ", "javaScript");
因為 網頁用 confirm去做確認框 但是 alert 情況也很像,所以都寫上當作範例
最後只要抓出簽到跟簽退的功能做按出就好了
但是因為,該網頁兩個功能都沒做id,把id作在上層的元件裡
所以造成無法直接抓出元件來點擊,小改了一下做法
HtmlElement divID = wbFrames.Document.GetElementById("有id的上層元件"); HtmlElementCollection items = divID .GetElementsByTagName("A"); // 因為是使用超聯結,所以抓出 tag "A" foreach (HtmlElement el in items) { string text = el.GetAttribute("InnerText"); if (text == "簽到") { el.InvokeMember("click"); break; } }
2018/01/15 補充兩個小片段
Google都能搜尋到的片段,只是為了方便,所以我也記錄一下
第一部分,寫在 static class Program裡面的 static void Main()
用來判斷是否有重複開啟軟體
// 是否可以開新的 bool canCreateNew; // 獲取程式的Guid 作為唯一標識 Attribute guid_attr = Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(GuidAttribute)); string guid = ((GuidAttribute)guid_attr).Value; // 利用 Mutex 判斷是否已經有開啟 var _mutex = new System.Threading.Mutex(true, guid, out canCreateNew); if (false == canCreateNew) { // 發現重複 MessageBox.Show("重複開啟", "重複開啟", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } _mutex.ReleaseMutex();
第二部分是 為了好看 讓程式 可以縮小到工作列圖示
首先是在共用地方宣告
//宣告NotifyIcon private System.Windows.Forms.NotifyIcon notifyIcon1;
然後再 Form 初始化的地方
設定 NotifyIcon 相關資訊,使用MouseDoubleClick 是為了讓他最小化可以還原
//指定使用的容器 this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components); //建立NotifyIcon this.notifyIcon1.Icon = new Icon("myIcon.ico"); this.notifyIcon1.Text = "AppName"; this.notifyIcon1.MouseDoubleClick += new MouseEventHandler(this.notifyIcon1_MouseDoubleClick);
另外加入 MouseDoubleClick 的 Function
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e) { this.Show(); this.WindowState = FormWindowState.Normal; }
另外要一進入就最小化的功能的話
請自行添加 Form1_Shown 事件,在裡面補充
private void Form1_Shown(object sender, EventArgs e) { if (bFirst) // 記錄是否為軟體第一次開啟 { bFirst = false; this.Hide(); this.notifyIcon1.Visible = true; this.notifyIcon1.ShowBalloonTip(5000, "開啟成功", "開啟成功", ToolTipIcon.Info); } }
寫在 Form1_Load 這沒用,因為這時候視窗還沒開啟