以前公司里面都是用vs自帶的webbrowser來做項目的,前兩天老板突然跟我說,這個項目內存頗大,可不可以用CefSharp替換webbrowser來縮減內存。然后我就踏入了CefSharp的坑……在這里把CefSharp使用整理一下,留着萬一以后會看看呢~
這邊放一個小demo,里面包含了我想把Cef封裝成一個自定義控件然后用IE的調用方式調用最后沒有成功的想法和一個多線程調用隊列
https://github.com/ruoruoniao/CefSharpDemo
下面進入正題~
-
添加依賴
在nuget中搜索CefSharp,用wpf的就直接下載CefSharp.Wpf,用winform的就下載CefSharp.Winforms,其他的依賴項會自動下載,然后重新打開一下VS,不然智能感知一直出問題不跳匹配項~
-
在設計器中添加ChromiumWebBrowser
最先需要用到CefSettings
1 private void InitializeCefSettings() 2 { 3 //全局下Cef只能被初始化一次 4 if(Cef.IsInitialized != true) 5 { 6 CefSettings settings = new CefSettings(); 7 //允許調用JS函數調用后端代碼 8 CefSharpSettings.LegacyJavascriptBindingEnabled = true; 9 CefSharpSettings.WcfEnabled = true; 10 //禁用代理設置 11 settings.CefCommandLineArgs.Add("no-proxy-server", "1"); 12 //禁用硬件加速 13 settings.CefCommandLineArgs.Add("disable-gpu", "1"); 14 Cef.Initialize(settings); 15 } 16 }
需要在你的控件或窗體初始化時加入這個函數,初始化Cef設置~
另外在加入這個函數的時候需要區分是否設計模式,不然設計器會拋出錯誤,代碼如下:
//winform版本 private bool IsDesignMode(System.Windows.Forms.UserControl ctl) { bool returnFlag = false; #if DEBUG if (LicenseManager.UsageMode == LicenseUsageMode.Designtime) { returnFlag = true; } else if (Process.GetCurrentProcess().ProcessName == "devenv") { returnFlag = true; } #endif return returnFlag; } //wpf版本 private bool IsDesignMode(System.Windows.Controls.UserControl ctl) { return System.ComponentModel.DesignerProperties.GetIsInDesignMode(ctl); }
輸入參數根據你的需要變動,因為我這里是把他們放到自定義控件中了所以是UserControl,如果是窗體的話就是Form或者Window了~
在wpf中(如果同時有其他的winform控件,且有可能winform控件可能會和Browser重合的情況)用到CefSharp需要用到winform版本的CefSharp,不然會出現瀏覽器在最下層其他在上層蓋住瀏覽器,導致始終鼠標點不到瀏覽器的情況,很尷尬……
但是如果沒有其他winform control的話(webbrowser也算winform control)可以直接使用wpf的版本
不過據說winform的版本性能比wpf的版本好,也不知道真的假的
Wpf:
用之前需要先添加命名空間到xaml
<!--沒有其他winform control的情況,使用wpf版本--> xmlns:CefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf" <!--有其他winform control的情況,使用Winform版本--> xmlns:CefSharpForWinform="clr-namespace:CefSharp.WinForms;assembly=CefSharp.WinForms"
有winform control:
用winformHost包裹住winform版本的ChromiumWebBrowser,Browser直接dock=fill,位置顯示什么的操作外面的winformhost
<WindowsFormsHost x:Name="showshengciPanel"d:IsLocked="True"> <CefSharpForWinform:ChromiumWebBrowser x:Name="showshengci"Dock="Fill"/> </WindowsFormsHost>
沒有winform control:
直接使用ChromiumWebBrowser
<CefSharp:ChromiumWebBrowser x:Name="WebBrowser" Loaded="LoadChromeBrowserComplete" FrameLoadEnd="LoadWebDocumentComplete" d:IsLocked="True"></CefSharp:ChromiumWebBrowser>
Winform:
winform的比較神奇,不可以添加在設計器中,只能在后台邏輯代碼中添加。
private void InitializeChromiumWebBrowser() { //之前上面那個Cef設置初始化 InitializeCefSettings(); this.WebBrowser = new ChromiumWebBrowser("about:blank"); this.WebBrowser.Dock = DockStyle.Fill; this.WebBrowser.Parent = this.WebBrowserPanel; }
將這個函數丟到當前控件或窗體的構造函數中,並且加上區分設計模式的代碼,不然vs可能爆炸(停止運行),這個我也不知道為啥,但是確實出現過這種情況……按說應該是彈出設計器錯誤才對但是就直接炸掉了……
-
在后台代碼中使用ChromiumWebBrowser
常用功能
這里我把ie常用的功能調用方式也貼過來了,大家可以看一下當個對照
另外如果wpf與winform使用方式有區別我會寫出來,不寫的話就代表沒有區別都是這種調用方式
重定向
//IE Webbrowser.Navigate("www.baidu.com"); //Cef //wpf中 ChromiumWebBrowser.Address = "www.baidu.com"; //winform中 ChromiumWebBrowserForWinform.Load("www.baidu.com");
文檔加載完成調用方法
//IE Webbrowser.LoadComplete += "LoadCompleted"; private void LoadCompleted(object sender, NavigationEventArgs e) { //邏輯代碼 } //Cef ChromiumWebBrowser.FrameLoadEnd += "FrameLoadEnded"; private void FrameLoadEnded(object sender, FrameLoadEndEventArgs e) { //邏輯代碼 }
調用頁面中的JS方法
//IE Webbrowser.InvokeScript("Init", new object[]{"aa"}); //Cef調用無返回值時 ChromiumWebBrowser.ExecuteScriptAsync("Init", new object[]{ "aa"});//或者 ChromiumWebBrowser.ExecuteScriptAsync("Init('aa')"); //Cef調用需要返回值時(無返回值時也可使用) //不等待返回值 ChromiumWebBrowser.EvaluateScriptAsync("Init", new object[]{"aa"});//或者 ChromiumWebBrowser.EvaluateScriptAsync("Init('aa')"); //等待返回值 Task<JavascriptResponse> result = ChromiumWebBrowser.EvaluateScriptAsync("Init", new object[]{"aa"});//用上面的第二種寫法也是可以的 string resultStr = result.Result.ToString();
網頁調用后台C#方法
//IE Webbrowser.ObjectForScripting = o; //Cef,只能在瀏覽器初始化的時候使用,不要在每一次頁面加載的時候都調用一次 ChromiumWebBrowser.JavascriptObjectRepository.Register("external", o, isAsync: false, options: new CefSharp.BindingOptions() { CamelCaseJavascriptNames = false }); //external隨你你想打啥打啥,如果你打一個jiuming在頁面中調用的時候就是jiuming.方法(); //后面那個BindingOptions()里面的東西是表示不使用駝峰命名法,也就是首字母為大寫的時候也能用 //如果不需要的話,options里面那一串可以用BindingOptions.DefaultBinder代替
取dom
//IE HTMLDocument document = Webbrowser.Document;//可以直接操作 //Cef IHTMLDocument2 document = (IHTMLDocument2)(new HTMLDocumentClass()); Task<JavascriptResponse> result = ChromiumWebBrowser.GetBrowser().MainFrame.GetSourceAsync(); document.write(result.Result.ToString()); return (HTMLDocument)document;//不可以直接操作就費這么大勁取出來只能讓你看看,Cef已經淡化了dom操作
-
踩過的一些坑
- 在FrameLoadEnd中調用需要等待返回值的JS函數時,如果直接使用上面的方法,會被卡死(我也沒搞明白為什么,有大佬能幫我看看嘛),所以需要采用以下方法
Task<JavascriptResponse> result = null; result = ChromiumWebBrowser.EvaluateScriptAsync("Init('aa')"); var waiter = result.GetAwiter(); waiter.OnCompleted(() => { string resultStr = waiter.Result.ToString(); })
- 如果想用dom操作的話,可以直接這樣子調用(如果在FarmeLoadEnd用上面寫的那種dom操作的話也會卡死~和等待返回值的JS函數調用一個道理):
//IE dom.documentElement.style.overflow = "hidden"; //Cef ChromiumWebBrowser.ExecuteScriptAsync("document.body.Style.overflow = 'hidden'");