以前公司里面都是用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'");