WPF中使用CEFSharp加载网页及交互


前言

现在常用的方案

  • Duilib+CEF 只支持Windows的选择,优点是打包文件小(使用C++) QQ、微信、有道精品课。
  • Qt+CEF 支持跨平台,缺点是打包文件大(使用C++)。
  • WPF/(WPF+CEFSharp) 打包文件小,但是性能相比前两者弱,但比Electron强,内存占用高,只支持Windows。
  • Electron 打包文件大,但是性能弱,内存占用高,支持跨平台。

几种方案都各有利弊,可以根据团队的情况选用,都是相对不错的,其他的方案比如Flutter,Java就不太推荐。

目前因为C++的技术栈的原因,我们的团队主要用WPF或者是Electron来做桌面端的开发。

有些界面用web开发会更好一点,所以这里就来集成CEFSharp来加载

注意

添加CEF会大幅增加安装包大小。

为什么使用CEF

  • .NET 自带的 WebBrowser 是WEB 开发人员最讨厌的 IE,性能低下而且兼容性差
  • Webkit: 项目已经不再支持
  • Cef 是 Chrome 内核,性能和兼容性杠杠的。缺点就是带的 DLL 太多太大,一个发布版应该在150M左右,X86+X64一块就得快300M了。另外EXE加载速度也会稍慢。

安装依赖

通过Nuget安装,右击项目 -> 管理Nuget程序包 -> 在打开的界面中搜索CefSharp,依次安装 CefSharp.CommonCefSharp.Wpf ,至于 cef.redist.x64cef.redist.x86会自动安装。

配置解决方案平台

因为CefSharp不支持Any CPU所以要配置x86、x64,点击菜单 生成 -> 配置管理器

选择解决方案平台,点击编辑,先将x64和x86删掉,再重新新建,重新配置比较容易些。

Any CPU的支持

如果我们要支持Any CPU就要自己实现了。

using System.Windows; using System; using System.Runtime.CompilerServices; using CefSharp; using System.IO; using System.Reflection; using System.Windows.Threading; using CefSharpWpfDemo.Log; namespace CEFSharpTest { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { public App() { // Add Custom assembly resolver AppDomain.CurrentDomain.AssemblyResolve += Resolver; //Any CefSharp references have to be in another method with NonInlining // attribute so the assembly rolver has time to do it's thing. InitializeCefSharp(); } [MethodImpl(MethodImplOptions.NoInlining)] private static void InitializeCefSharp() { var settings = new CefSettings(); // Set BrowserSubProcessPath based on app bitness at runtime settings.BrowserSubprocessPath = Path.Combine( AppDomain.CurrentDomain.SetupInformation.ApplicationBase, Environment.Is64BitProcess ? "x64" : "x86", "CefSharp.BrowserSubprocess.exe" ); // Make sure you set performDependencyCheck false Cef.Initialize(settings, performDependencyCheck: false, browserProcessHandler: null); } // Will attempt to load missing assembly from either x86 or x64 subdir // Required by CefSharp to load the unmanaged dependencies when running using AnyCPU private static Assembly Resolver(object sender, ResolveEventArgs args) { if (args.Name.StartsWith("CefSharp")) { string assemblyName = args.Name.Split(new[] { ',' }, 2)[0] + ".dll"; string archSpecificPath = Path.Combine( AppDomain.CurrentDomain.SetupInformation.ApplicationBase, Environment.Is64BitProcess ? "x64" : "x86", assemblyName ); return File.Exists(archSpecificPath) ? Assembly.LoadFile(archSpecificPath) : null; } return null; } } }

使用

使用时可以直接在xaml文件中直接添加ChromiumWebBrowser控件,不过ChromiumWebBrowser控件特别消耗内存,所以代码里动态添加也是一种不错的选择。

在xaml中添加浏览器

xmal文件头部插入引用

xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"

添加控件如下:

<Grid x:Name="ctrlBrowerGrid"> <wpf:ChromiumWebBrowser x:Name="Browser"/> </Grid>

cs文件中操作控件访问网址:

Browser.Load("https://www.psvmc.cn");

代码添加浏览器

添加浏览器类:

using CefSharp.Wpf; using System.ComponentModel; using System.Windows; namespace CEFSharpTest.view { internal sealed class CollapsableChromiumWebBrowser : ChromiumWebBrowser { public CollapsableChromiumWebBrowser() { Loaded += BrowserLoaded; } private void BrowserLoaded(object sender, RoutedEventArgs e) { // Avoid loading CEF in designer if (DesignerProperties.GetIsInDesignMode(this)) { return; } // Avoid NRE in AbstractRenderHandler.OnPaint ApplyTemplate(); } } }

动态添加和操作控件:

private CollapsableChromiumWebBrowser MyBrowser = null; private void InitWebBrower() { MyBrowser = new CollapsableChromiumWebBrowser(); //页面插入控件 ctrlBrowerGrid.Children.Add(MyBrowser); //这里不能用Load()的方法,会报错。 MyBrowser.Address = "https://www.psvmc.cn"; }

获取Cookie和Html

添加Cookie访问类

using CefSharp;

using System; namespace CEFSharpTest.view { public class CookieVisitor : ICookieVisitor { private string Cookies = null; public event Action<object> Action; public bool Visit(Cookie cookie, int count, int total, ref bool deleteCookie) { if (count == 0) Cookies = null; Cookies += cookie.Name + "=" + cookie.Value + ";"; deleteCookie = false; return true; } public void Dispose() { if (Action != null) Action(Cookies); return; } } }

浏览器控件访问网址,并设置回调

private CollapsableChromiumWebBrowser MyBrowser = null; private void InitWebBrower() { MyBrowser = new CollapsableChromiumWebBrowser(); //页面插入控件 ctrlBrowerGrid.Children.Add(MyBrowser); MyBrowser.FrameLoadEnd += Browser_FrameLoadEnd; //这里不能用Load()的方法,会报错。 MyBrowser.Address = "https://www.psvmc.cn"; } private async void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) { CookieVisitor visitor = new CookieVisitor(); string html = await MyBrowser.GetSourceAsync(); Console.WriteLine("html:" + html); visitor.Action += RecieveCookie; Cef.GetGlobalCookieManager().VisitAllCookies(visitor); return; } public async void RecieveCookie(object data) { string cookies = (string)data; Console.WriteLine("cookies:" + cookies); return; }

加载本地页面和JS回调

添加HTML

项目下添加html路径html\index.html

<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8" /> <script type="text/javascript"> function callback() { callbackObj.showMessage('message from js'); } function alert_msg(msg) { alert(msg); } </script> </head> <body> <button onclick="callback()">Click</button> <style> * { margin: 0; padding: 0; } body { background-color: #f3f3f3; width: 100vw; height: 100vh; display:flex; align-items:center; justify-content:center; } </style> </body> </html>

复制页面到目标目录

方式1

项目->属性->生成事件->生成前事件命令行

添加如下

xcopy /Y /i /e $(ProjectDir)\html $(TargetDir)\html

方式2

文件右键点击属性,设置复制到输出目录和生成操作。

如果文件较多建议用方式1 。

代码

注册一个JS对象

private ChromiumWebBrowser MyBrowser = null; private void InitWebBrower() { CefSettings cSettings = new CefSettings() { Locale = "zh-CN", CachePath = Directory.GetCurrentDirectory() + @"\Cache" }; cSettings.MultiThreadedMessageLoop = true; cSettings.CefCommandLineArgs.Add("proxy-auto-detect", "0"); cSettings.CefCommandLineArgs.Add("--disable-web-security", ""); //Disable GPU acceleration cSettings.CefCommandLineArgs.Add("disable-gpu"); //Disable GPU vsync cSettings.CefCommandLineArgs.Add("disable-gpu-vsync"); //此配置可以允许摄像头打开摄像 cSettings.CefCommandLineArgs.Add("enable-media-stream", "1"); Cef.Initialize(cSettings); string pagepath = string.Format(@"{0}html\index.html", AppDomain.CurrentDomain.BaseDirectory); if (!File.Exists(pagepath)) { MessageBox.Show("HTML不存在: " + pagepath); return; } // Create a browser component MyBrowser = new ChromiumWebBrowser(); //禁用右键菜单 MyBrowser.MenuHandler = new MenuHandler(); //禁用弹窗 MyBrowser.LifeSpanHandler = new LifeSpanHandler(); MyBrowser.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#f3f3f3")); //页面插入控件 ctrlBrowerGrid.Children.Add(MyBrowser); MyBrowser.Address = pagepath; MyBrowser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true; MyBrowser.JavascriptObjectRepository.Register( "callbackObj", new CallbackObjectForJs(), isAsync: true, options: BindingOptions.DefaultBinder ); }

调用JS方法

private void Button_Click(object sender, RoutedEventArgs e) { MyBrowser.ExecuteScriptAsync("alert_msg('123')"); }

事件回调类

public class CallbackObjectForJs { public void showMessage(string msg) { MessageBox.Show(msg); } }

禁用右键菜单的类

public class MenuHandler : IContextMenuHandler { public void OnBeforeContextMenu( IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model ) { model.Clear(); } public bool OnContextMenuCommand( IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags ) { return false; } public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) { } public bool RunContextMenu( IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback ) { return false; } }

原窗口打开链接的类

public class LifeSpanHandler : ILifeSpanHandler { //弹出前触发的事件 public bool OnBeforePopup( IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) { //使用源窗口打开链接,取消创建新窗口 newBrowser = null; var chromiumWebBrowser = (ChromiumWebBrowser)webBrowser; chromiumWebBrowser.Load(targetUrl); return true; } public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser) { } public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser) { return true; } public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser) { } }

注意项

API变更

//Old Method
MyBrowser.RegisterAsyncJsObject("callbackObj", new CallbackObjectForJs(), options: BindingOptions.DefaultBinder); //Replaced with MyBrowser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true; MyBrowser.JavascriptObjectRepository.Register("callbackObj", new CallbackObjectForJs(), isAsync: true, options: BindingOptions.DefaultBinder);

本地文件路径

文件路径中不能包含特殊字符,否则不能加载,之前我的项目在C#目录下,就一直加载不了页面。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM