【WPF】桌面程序使用谷歌浏览器内核CefSharp控件详解

有过从事前端开发经验的工作者,还想要开发最好的桌面程序,最好是能继承前端技术UI设计界面的优势,这里讲一讲如何集成主流浏览器控件的方案。

这里用的是CefSharp.Wpf的插件的解决方案,

该插件是基于Chromium内核的开源框架做出来的,

  • 支持Windows/Linux/macOS平台
  • 提供完整的浏览器功能,包括HTML5、CSS3、JavaScript等
  • 典型应用:VS Code、Spotify等

当前趋势:

  • Chromium内核已成为主流选择(CEF、WebView2)
  • 微软已放弃旧版IE/EdgeHTML内核,改用Chromium,
  • Chromium基于WebKit,WebKit在苹果生态中仍保持优势

开发者更倾向选择跨平台、维护活跃的方案桌面程序使用主流浏览器控件有哪些呢,肯定是用weikit内核的

回到正题,本文章将以开发WPF(Windows Presentation Foundation)桌面应用程序为例进行讲解。

基于Microsoft的.NET Framework 4.7.2及以上版本进行开发,这是一套成熟的Windows应用程序开发框架,提供了丰富的UI控件库和强大的数据绑定功能。

在座的同学需要熟悉以下开发环境和工具,动手时准备好Windows系统的电脑:

  1. 开发工具:Visual Studio 2022社区版或专业版(2020版本亦可);
  2. 编程语言:使用C# 8.0及以上版本;
  3. 使用开发工具创建好一个WPF项目例子

文章目录

WebBrowser

开发过项目的同学都知道,这是开发工具组件工具箱内置的控件,是桌面程序的常见浏览器控件,

使用控件

在页面布局文件里,添加的控件

xml 复制代码
<WebBrowser x:Name="webBrowser1"/>

此控件使用的IE内核,浏览很多网页会有兼容问题,

渲染简单HTML是没有问题的,常见于帮助文档,展示使用协议等...

如果是想要主流浏览器的Web页面渲染一样的效果还是有问题的,

如有需要,这里就用替代方案,

接下来,解决问题

CefSharp.Wpf

这是类似谷歌浏览器内核webkit的插件,

安装控件

此插件可以在NeGet包管理器上找到,安装后如下图

注意选CefSharp.Wpf安装前,留意看看描述下的依赖版本,其它的是相关依赖,会自动安装在一起;

  • 若安装失败,请检查项目属性设置的.Net Framework框架版本是否低于插件的依赖版本,如果是,就往上提高一下项目的框架版本再试;
  • 项目的框架版本越低,就能运行在越旧的电脑Windows系统版本上;

使用控件

在页面布局文件里,这样添加控件

xml 复制代码
<Window x:Class="WpfApp2.MainWindow"
		...
        xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <cefSharp:ChromiumWebBrowser x:Name="browser" />
    </Grid>
</Window>

在写入使用的控件cefSharp时,按下保存键,需要留意到输入位置的自动引用提示,按确定后会自动添加引用,如果没有,需手动填写,声明引用xmlns:cefSharp=...

初始化配置

添加CefSharp引用,

在页面代码里,这样做

csharp 复制代码
public partial class MainWindow : Window
{
    public MainWindow()
    {
        CefSharp.Cef.Initialize(); // 确保CEF初始化
        InitializeComponent();
        //如果没有在xaml页面布局中添加此控件,就在代码中添加
        //browser = new ChromiumWebBrowser();
    }
}

注意编译目标平台要选x64或者x86,否则编译会报错

用户设置

在初始化里,修改此控件的用户设置

csharp 复制代码
var settings = new CefSharp.Wpf.CefSettings();
settings.LogSeverity = CefSharp.LogSeverity.Verbose;
settings.CachePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache");

settings.Locale = "zh-CN";
settings.AcceptLanguageList = "zh-CN";
settings.UserAgent = "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Mobile Safari/537.36";
CefSharp.Cef.Initialize(settings);

注意上面的初始化一段代码要放在初始化代码调用InitializeComponent();方法之前,否则运行会抛出异常;

不同的CefSharp.wpf版本的初始化步骤中的CefSettings配置属性, 和一些方法会有所改动,部分可能会不支持;

考虑到新手看不懂一些配置项怎么按需求设置,百度Ai一下,你就知道了

初始化的代码不应该重复调用,否则会报错,

建议把初始化代码放在程序执行入口开始时执行一次,

要判断是否已初始化,代码如下

csharp 复制代码
var isInitialized = false;
try
{
    isInitialized = Cef.IsInitialized ?? false;
}
catch(Exception ex)
{
    string errMsg = ex.Message;
    //...
    throw new Exception(errMsg);
}

判断初始化代码可能会抛出异常:

System.IO.FileNotFoundException:"未能加载文件或程序集"CefSharp.Core.Runtime, Version=140.1.140.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138"或它的某一个依赖项。系统找不到指定的文件。"
尝试解决:需要把项目属性中的编译目标Any CPU 改为 x64,再编译运行

自带功能

此控件有自带了一些基本的功能

访问地址

调用控件的Navigate方法,访问地址

csharp 复制代码
var url = "https://www.baidu.com/";

browser.Navigate(url);

例如打开开发者工具,这是开发者常用到的调试必备工具,

其它基本功能就不多了,请自行探索

开发者工具

再调用浏览器控件的方法即可打开,

代码如下

csharp 复制代码
using CefSharp;

//打开DevTools
browser.ShowDevTools();

查看网页源码

查看网页源码时异步操作的,

在初始化里添加,网页加载完成时处理事件

csharp 复制代码
browser1.FrameLoadEnd += OnFrameLoadEnd;

当网页加载完成时,就可以看到源码

csharp 复制代码
private void OnFrameLoadEnd(object sender, CefSharp.FrameLoadEndEventArgs e)
{
    e.Frame.GetSourceAsync().ContinueWith(t =>
    {
        Debug.WriteLine($">>> OnFrameLoadEnd Source:{t.Result}");
    });
}

执行网页脚本

要等网页加载完才可以执行脚本

csharp 复制代码
var jsCode = "alert(\"Hello CefSharp\");";

browser.Load("https://www.baidu.com");
browser.ExecuteScriptAsyncWhenPageLoaded(jsCode);

要立即执行脚本,

csharp 复制代码
var jsCode = "alert(\"Hello CefSharp\");";

browser.ExecuteScriptAsync(jsCode);

执行后需要返回结果

csharp 复制代码
var jsCode = "(function(){alert(\"Hello CefSharp\");})()";

browser.EvaluateScriptAsync(jsCode).ContinueWith(res => {
	JavascriptResponse jRes = res.Result;
	Debug.WriteLine($">>> evaluate sj is success res:{jRes.Result}");
});

其中,jRes.Result返回的对象类型,有dynamic或者List<dynamic>;

实现功能

此控件默认是没有浏览器常见功能的,

不过有提供接口,需要自己实现过程,

处理打开链接

默认是创建新窗口打开新链接,

要在同一窗口中打开,

在初始化时,添加如下代码,

csharp 复制代码
browser.LifeSpanHandler = new OpenPageSelf();

新建一个处理类,重写LifeSpanHandler类的方法,代码如下

csharp 复制代码
class OpenPageSelf : LifeSpanHandler
{
    public OpenPageSelf() : base()
    {
    }

    override
    protected bool OnBeforePopup(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser)
    {
        // 限制在同一窗口中加载
        chromiumWebBrowser.Load(targetUrl);
        newBrowser = null;
        return true;
    }
}

禁用右键菜单

在初始化里,设置代码如下

csharp 复制代码
browser.MenuHandler = new OpenMenuPanel();

新建一个处理类,实现继承IContextMenuHandler的方法

csharp 复制代码
class OpenMenuPanel : IContextMenuHandler
{
    public void OnBeforeContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model)
    {
        model.Clear();
    }

    public bool OnContextMenuCommand(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags)
    {
        return false;
    }

    public void OnContextMenuDismissed(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
    {
    
    }

    public bool RunContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback)
    {
        return false;//这里禁用
    }
}

处理下载文件

此控件默认是未实现下载功能的,

在初始化里,实现下载功能

csharp 复制代码
browser.DownloadHandler = new DownloadHandler();

新建一个下载类,实现继承IDownloadHandler的方法

csharp 复制代码
class DownloadHandler : CefSharp.IDownloadHandler
{

    public bool CanDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, string url, string requestMethod)
    {
        return true;//返回true可下载
    }

    public void OnBeforeDownload(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, IBeforeDownloadCallback callback)
    {
        if (string.IsNullOrEmpty(downloadItem.FullPath))
        {
            var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString());
            System.IO.Directory.CreateDirectory(path);
            downloadItem.FullPath = System.IO.Path.Combine(path, downloadItem.SuggestedFileName);
        }
        
        if (callback.IsDisposed)
        {
            return;
        }

        using (callback)
        {
            //设置初始下载路径,是否打开保存文件对话框
            callback.Continue(downloadItem.FullPath, true);
        }
    }

    public void OnDownloadUpdated(IWebBrowser chromiumWebBrowser, IBrowser browser, DownloadItem downloadItem, IDownloadItemCallback callback)
    {
        var fileName = downloadItem.FullPath;
        if (downloadItem.IsInProgress)
        {
            //下载中
        }
        else if (downloadItem.IsComplete)
        {
            //此处下载完成,可以处理一些,例如通知,打开文件
        }
    }
}

使用证书访问

如果是https加密访问的话,默认可能会无法正常访问,提示证书安全问题,

初始化里设置到浏览器控件中

csharp 复制代码
browser.RequestHandler = new ExampleRequestHandler();

新建一个请求处理类ExampleRequestHandler,继承它的方法RequestHandler

可以采取一些措施来忽略证书错误,,代码如下

csharp 复制代码
public class ExampleRequestHandler : RequestHandler
{
   protected override bool OnCertificateError(IWebBrowser chromiumWebBrowser, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback)
   {
       Task.Run(() =>
       {
           if (!callback.IsDisposed)
           {
               using (callback)
               {
                   // 如果URL包含特定字符串,则忽略证书错误
                   if (requestUrl.Contains("reportSystem"))
                   {
                       callback.Continue(true); // 重点部分
                   }
                   else
                   {
                       callback.Continue(false);
                   }
               }
           }
       });
       return true;
   }
}

或者,在初始化的设置CefSettings中添加配置项,代码如下

csharp 复制代码
// 忽略HTTPS证书的问题
settings.CefCommandLineArgs.Add("--ignore-urlfetcher-cert-requests", "1");
settings.CefCommandLineArgs.Add("--ignore-certificate-errors", "1");
// 禁止启用同源策略安全限制,禁止后不会出现跨域问题
settings.CefCommandLineArgs.Add("--disable-web-security", "1");

使用问题

脚本执行问题

执行脚本代码抛出异常,

使用不同的CefSharp.Wpf版本,新版的对脚本的执行方式做了安全限制,

可能需要在初始化里添加以下配置

csharp 复制代码
browser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
//browser.JavascriptObjectRepository.Settings.JavascriptBindingApiEnabled = true;

FileLoadException

编译通过, 运行时会抛出异常,如下

"System.IO.FileLoadException"类型的未经处理的异常在 CefSharp.Wpf.dll 中发生
是不是改变了库版本哦, 或者改了项目框架版本,

在解决方案名称上鼠标右键,弹出如下图, 选择 管理解决方案的 NuGet程序包

这时, NuGet管理器上会多出一个合并选项卡`, 看看有没有,

有的话, 处理合并, 这也是冲突的缘故,

没有的话, 重新安装CefSharp的其它版本看看,

先卸载了, 重新安装

卸载完之前留意自动安装依赖的其它项, 发现管理器多出了哪两个, 那是没有经过自己手动安装的, 也一起卸了,卸载干净重新安装试试,

会不会是这个引起的冲突呢

如果这次还抛出异常了, 开发工具下控制台输出的调试信息, 如下

FileLoadException: 未能加载由"CefSharp.Core.Runtime.dll"导入的过程

检查包, 链接库版本冲突

估计是文件占用了, CefSharp.Wpf 运行初始化后未正常关闭,需要在程序关闭时执行如下代码

复制代码
if (Cef.IsShutdown != true)
{
    Cef.Shutdown();
}

若还是不行,将编译输出的所有文件都清空, 重新编译运行看看呢;
考虑到新手在实现时可能会没招,可以试试求助万能的AI...

相关项目

如果你觉得使用此控件安装包体积过大,可以考虑使用轻便的控件Geckofx,如下

桌面程序使用火狐浏览器内核Geckofx控件详解
写到这里,到此告一段落...

考虑到新手会嫌写代码一个个实现太慢,有没有速成的方法,可以参考作者开发过的浏览器相关项目源码

把自己认为最好的在线AI智能助手写到一个自用浏览器里,好主意!

在需要的时候按一键就用上了,同时不用担心装在电脑里的AI读取你的使用记录...(不可能 )

相关推荐
Macbethad14 小时前
工业设备数据采集主站程序技术方案
wpf
关关长语1 天前
HandyControl 3.5.x 版本 ListViewItem不显示问题
windows·wpf
Macbethad1 天前
工业设备维护程序技术方案
wpf
Macbethad1 天前
工业设备配方管理系统技术方案
wpf
喵叔哟1 天前
7.日志系统深入
wpf
清风徐来Groot1 天前
WPF布局之Grid
wpf
T___T1 天前
偷看浏览器后台,发现它比我忙多了
前端·浏览器
清风徐来Groot1 天前
WPF布局之WrapPanel
wpf
Macbethad1 天前
WPF工业设备工艺配方流程程序技术方案
wpf