• 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#目录下,就一直加载不了页面。

  • 相关阅读:
    Python加载声音
    Python 的文件处理
    java学习总结
    Fiddler二次开发 C#
    开发工具 快捷键
    linux / shell /adb
    Java堆栈
    selenium获取接口 HAR
    服务端通过socket向安卓客户端发送shell
    设计模式
  • 原文地址:https://www.cnblogs.com/lzjsky/p/15891469.html
Copyright © 2020-2023  润新知