基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用

news/2024/7/9 23:53:00 标签: .net, vue, c#

零、参考资料

1、https://github.com/cefsharp/CefSharp/wiki/Quick-Start-For-MS-.Net-5.0-or-greater

2、https://github.com/cefsharp/CefSharp/wiki/Quick-Start

3、https://github.com/cefsharp/CefSharp/wiki/General-Usage#javascript-integration

一、安装 Nuget 包

https://www.nuget.org/packages/CefSharp.WinForms

安装 CefSharp.WinForms 包后会显示 Readme.txt,

二、配置项目

<!-- CefWindowsFormsApp.csproj -->
<PropertyGroup Condition="'$(PlatformTarget)' == 'x64'">
  <RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == ''">win-x64</RuntimeIdentifier>
  <SelfContained Condition="'$(SelfContained)' == ''">false</SelfContained>
</PropertyGroup>

三、加载远程网页示例

使用 ChromiumWebBrowser 加载百度首页,

// Form1.cs
using CefSharp;
using CefSharp.WinForms;
using System.Windows.Forms;

namespace CefWindowsFormsApp
{
    public partial class Form1 : Form
    {
        private static ChromiumWebBrowser browser;

        public Form1()
        {
            InitializeComponent();
            AddChromiumWebBrowser();
        }

        /// <summary>
        /// Create a new instance in code or add via the designer
        /// </summary>
        private void AddChromiumWebBrowser()
        {
            browser = new ChromiumWebBrowser("www.baidu.com");
            this.Controls.Add(browser);
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Load a url
            browser.LoadUrl("https://www.baidu.com/");
        }
    }
}

这这个示例中,我们引入了 CefSharp 库,在 Form1 窗体中添加了浏览器控件 ChromiumWebBrowser ,并且在窗体启动时加载百度首页,

四、加载本地网页示例

首先创建一个 Vue 项目,

# 使用 Vite
cnpm create vite@latest

# cd vite-project
# cnpm i

接着完成前端的开发之后,打包静态资源,

npm run build

然后在 WinForm 项目下创建 Resources 文件夹,把前端打包的 dist 文件夹下的文件全部复制过来,并且文件属性设置为“嵌入的资源”,

最后通过 RegisterScheme 注册为本地资源访问,

// Form1.cs
using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.WinForms;
using System.Windows.Forms;

namespace CefWindowsFormsApp
{
    public partial class Form1 : Form
    {
        private static ChromiumWebBrowser browser;

        public Form1()
        {
            InitializeComponent();
            AddChromiumWebBrowser();
        }

        /// <summary>
        /// Create a new instance in code or add via the designer
        /// </summary>
        private void AddChromiumWebBrowser()
        {
            InitBrowser();

            browser = new ChromiumWebBrowser("http://cefsharp.test");
            this.Controls.Add(browser);

        }

        public static void InitBrowser()
        {
            // Pseudo code; you probably need more in your CefSettings also.
            var settings = new CefSettings();

            settings.RegisterScheme(new CefCustomScheme
            {
                SchemeName = "http",
                DomainName = "cefsharp.test",
                SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\..\CefWindowsFormsApp\Resources",
                            hostName: "cefsharp.test", //Optional param no hostname/domain checking if null
                            defaultPage: "index.html") //Optional param will default to index.html
            });

            Cef.Initialize(settings);
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Load local url
            browser.LoadUrl("http://cefsharp.test");
        }       
    }
}

效果如下,

五、调用 JS 代码

1、JS 无返回值

窗体页面调整,新增一个 Button 、一个 Pane,

注意,JavaScript 只能在 V8Context 中执行,这里的示例通过实现 IRenderProcessMessageHandler 接口、绑定 LoadingStateChanged 、FrameLoadEnd 两个事件来实现 DOM 加载时弹窗、页面资源加载完毕弹窗、主页面加载完毕弹窗,通过 “DoAlert” 按钮绑定事件来实现手动弹窗,

// Form1.cs
using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.WinForms;
using System.Windows.Forms;

namespace CefWindowsFormsApp
{
    public partial class Form1 : Form
    {
        private static ChromiumWebBrowser browser;

        public Form1()
        {
            InitializeComponent();
            AddChromiumWebBrowser();
        }

        /// <summary>
        /// Create a new instance in code or add via the designer
        /// </summary>
        private void AddChromiumWebBrowser()
        {
            InitBrowser();

            browser = new ChromiumWebBrowser("http://cefsharp.test");
            browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();
            // Wait for the page to finish loading (all resources will have been loaded, rendering is likely still happening)
            
            browser.LoadingStateChanged += (sender, args) =>
            {
                // Wait for the Page to finish loading
                if (args.IsLoading == false)
                {
                    browser.ExecuteScriptAsync("alert('All Resources Have Loaded');");
                }
            };

            // Wait for the MainFrame to finish loading
            browser.FrameLoadEnd += (sender, args) =>
            {
                // Wait for the MainFrame to finish loading
                if (args.Frame.IsMain)
                {
                    args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");
                }
            };
            BrowserPanel.Controls.Add(browser);

        }

        public static void InitBrowser()
        {
            // Pseudo code; you probably need more in your CefSettings also.
            var settings = new CefSettings();

            settings.RegisterScheme(new CefCustomScheme
            {
                SchemeName = "http",
                DomainName = "cefsharp.test",
                SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\..\CefWindowsFormsApp\Resources",
                            hostName: "cefsharp.test", // Optional param no hostname/domain checking if null
                            defaultPage: "index.html") // Optional param will default to index.html
            });

            Cef.Initialize(settings);
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Load a url
            browser.LoadUrl("http://cefsharp.test");
        }


        public class RenderProcessMessageHandler : IRenderProcessMessageHandler
        {
            public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
            {
                throw new System.NotImplementedException();
            }

            public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
            {
                throw new System.NotImplementedException();
            }

            public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
            {
                throw new System.NotImplementedException();
            }

            // Wait for the underlying JavaScript Context to be created. This is only called for the main frame.
            // If the page has no JavaScript, no context will be created.
            void IRenderProcessMessageHandler.OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
            {
                const string script = "document.addEventListener('DOMContentLoaded', function(){ alert('DomLoaded'); });";

                frame.ExecuteJavaScriptAsync(script);
            }
        }

        private void DoAlertBtn_Click(object sender, System.EventArgs e)
        {
            browser.ExecuteScriptAsync("alert('Hello World!');");
        }

    }
}

效果如下,

2、JS 有返回值

        private async void DoAlertBtn_ClickAsync(object sender, System.EventArgs e)
        {
            var script = @"(function() { let val = 1 + 1; return val; })();";
            JavascriptResponse response = await browser.GetBrowser().MainFrame.EvaluateScriptAsync(script);
            browser.ExecuteScriptAsync(string.Format("alert(' 1 + 1 = {0}');", (int)response.Result));
        }

六、Browser 调试器

browser.ShowDevTools();

七、app.manifest 清单文件

该清单文件是为了兼容 Win7、8、10 等系统差异,

<!-- example.exe.manifest -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <assemblyIdentity
        type="win32"
        name="Contoso.ExampleApplication.ExampleBinary"
        version="1.2.3.4"
        processorArchitecture="x86"
    />
    <description>Contoso Example Application</description>
    <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
        <application>
            <!-- Windows 10/11 -->
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- * ADD THIS LINE * -->
        </application>
    </compatibility>
</assembly>

八、JS 调用 .Net 方法

官方推荐使用异步版本,因为同步版本在 .Net Core 或者 .Net 5 不再支持。

1、封装一个公共方法

// CommonUtil.cs
using System;

namespace CefWindowsFormsApp.Func
{
    /// <summary>
    /// 通用工具类封装
    /// </summary>
    public class CommonUtil
    {
        /// <summary>
        /// 获取全局唯一 id
        /// </summary>
        /// <returns> string </returns>
        public string GetGuid()
        {
            return new Guid().ToString();
        }
    }
}

2、使用 JavascriptObjectRepository 注册一个类实例

注册一个 CommonUtil 类实例,具体代码封装在方法 ExposeDotnetClass() 中,然后在构造方法中调用,

// Form1.cs
using CefSharp;
using CefSharp.JavascriptBinding;
using CefSharp.SchemeHandler;
using CefSharp.WinForms;
using CefWindowsFormsApp.Func;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace CefWindowsFormsApp
{
    public partial class Form1 : Form
    {
        private static ChromiumWebBrowser browser;

        public Form1()
        {
            InitializeComponent();
            AddChromiumWebBrowser();
        }

        /// <summary>
        /// Create a new instance in code or add via the designer
        /// </summary>
        private void AddChromiumWebBrowser()
        {
            InitBrowser();

            browser = new ChromiumWebBrowser("http://cefsharp.test");
            browser.RenderProcessMessageHandler = new RenderProcessMessageHandler();
            // Wait for the page to finish loading (all resources will have been loaded, rendering is likely still happening)
            
            browser.LoadingStateChanged += (sender, args) =>
            {
                // Wait for the Page to finish loading
                if (args.IsLoading == false)
                {
                    browser.ExecuteScriptAsync("alert('All Resources Have Loaded');");
                }
            };

            // Wait for the MainFrame to finish loading
            browser.FrameLoadEnd += (sender, args) =>
            {
                // Wait for the MainFrame to finish loading
                if (args.Frame.IsMain)
                {
                    args.Frame.ExecuteJavaScriptAsync("alert('MainFrame finished loading');");
                }
            };

            ExposeDotnetClass();

            BrowserPanel.Controls.Add(browser);

        }

        public static void InitBrowser()
        {
            // Pseudo code; you probably need more in your CefSettings also.
            var settings = new CefSettings();

            settings.RegisterScheme(new CefCustomScheme
            {
                SchemeName = "http",
                DomainName = "cefsharp.test",
                SchemeHandlerFactory = new FolderSchemeHandlerFactory(rootFolder: @"..\..\..\..\CefWindowsFormsApp\Resources",
                            hostName: "cefsharp.test", // Optional param no hostname/domain checking if null
                            defaultPage: "index.html") // Optional param will default to index.html
            });

            Cef.Initialize(settings);
        }

        /// <summary>
        /// 导出类方法
        /// </summary>
        public static void ExposeDotnetClass()
        {
            browser.JavascriptObjectRepository.ResolveObject += (sender, e) =>
            {
                var repo = e.ObjectRepository;
                if (e.ObjectName == "commonUtil")
                {
                    BindingOptions bindingOptions = null;
                    bindingOptions = BindingOptions.DefaultBinder;
                    repo.NameConverter = null;                
                    repo.NameConverter = new CamelCaseJavascriptNameConverter();
                    repo.Register("commonUtil", new CommonUtil(), isAsync: true, options: bindingOptions);
                }
            };

            browser.JavascriptObjectRepository.ObjectBoundInJavascript += (sender, e) =>
            {
                var name = e.ObjectName;
                Debug.WriteLine($"Object {e.ObjectName} was bound successfully.");
            };

        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            // Load a url
            browser.LoadUrl("http://cefsharp.test");
        }


        public class RenderProcessMessageHandler : IRenderProcessMessageHandler
        {
            public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
            {
                throw new System.NotImplementedException();
            }

            public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode node)
            {
                throw new System.NotImplementedException();
            }

            public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
            {
                throw new System.NotImplementedException();
            }

            // Wait for the underlying JavaScript Context to be created. This is only called for the main frame.
            // If the page has no JavaScript, no context will be created.
            void IRenderProcessMessageHandler.OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
            {
                const string script = "document.addEventListener('DOMContentLoaded', function(){ alert('DomLoaded'); });";

                frame.ExecuteJavaScriptAsync(script);
            }
        }

        private async void DoAlertBtn_ClickAsync(object sender, System.EventArgs e)
        {
            var script = @"(function() { let val = 1 + 1; return val; })();";
            JavascriptResponse response = await browser.GetBrowser().MainFrame.EvaluateScriptAsync(script);
            browser.ExecuteScriptAsync(string.Format("alert(' 1 + 1 = {0}');", (int)response.Result));
        }

        private void ShowDevToolBtn_Click(object sender, System.EventArgs e)
        {
            browser.ShowDevTools();
        }
    }
}

3、新建一个 Vue 项目

vue create cefvue -m cnpm

npm run build

运行,

发布

项目右键,选择 Release x64 模式“重新生成”,在 bin 目录下生成文件,

需要把该目录的所有文件复制到另一台相同 .Net 版本的电脑才能运行。


http://www.niftyadmin.cn/n/5122705.html

相关文章

0034【Edabit ★☆☆☆☆☆】【修改Bug4】Buggy Code (Part 4)

0034【Edabit ★☆☆☆☆☆】【修改Bug4】Buggy Code (Part 4) bugs conditions strings Instructions Emmy has written a function that returns a greeting to users. However, she’s in love with Mubashir, and would like to greet him slightly differently. She add…

如何在VScode中让printf输出中文

如何在VScode中让printf输出中文&#xff1f; 1、在“Visual Studio Code”图标上右击&#xff0c;弹出对话框。见下图&#xff1a; 2、点击“以管理员身份运行”&#xff0c;得到下图&#xff1a; 3、点击“UTF-8”按钮&#xff0c;得到下图&#xff1a; 4、点击“通过编码重…

C#(JS)几个常用的正则表达式

快一个月没更新了&#xff0c;今天写几个比较常用的正则表达式。供大家参考。 Regex rg new Regex(".*[a-zA-z].*"); //判断是否包含字母 public static boolean isLetterDigit(String str) { //return (rg.IsMatch("34(AS)"); >true //return (rg…

运维部署宝典-长更版

亲测、简洁、长更 防火墙操作&#xff1a;安装vm16&#xff1a;安装centos&#xff08;重点&#xff1a;重启ip会替换&#xff09;:安装redis&#xff1a;主从复制&#xff1a;哨兵模式&#xff1a; java安装&#xff1a;安装nginx&#xff1a;elkf搭建&#xff1a;elasticsear…

C# 单实例运行

在C#中实现单实例运行通常涉及使用互斥体&#xff08;Mutex&#xff09;来确保只有一个实例可以运行。下面是一种常见的实现方法&#xff1a; using System; using System.Threading; using System.Windows.Forms;class Program {static void Main(){bool createdNew;Mutex mu…

基于Laravel封装一个强大的请求响应日志记录中间件

为何强大 记录全面&#xff1a; 包含请求路径、请求方法、客户端IP、设备标识、荷载数据、文件上传、请求头、业务逻辑处理时间、业务逻辑所耗内存、用户id、以及响应数据。配置简单&#xff1a; 默认不需要写任何逻辑可开箱即用&#xff0c;靠前4个方法&#xff0c;就可指定某…

el-tree横向纵向滚动条

el-tree未展开时样式 el-tree展开时样式 给容器一个高度&#xff0c;然后样式加上overflow: scroll&#xff0c;这样纵向滚动条就出来了。 <el-card style"height: 528px;overflow: scroll"><el-inputplaceholder"输入关键字进行过滤"v-model&…

Spring读书笔记——bean创建(上)

Spring读书笔记——bean创建&#xff08;上&#xff09; 目录 Spring读书笔记——bean创建&#xff08;上&#xff09; 从getBean说起 AbstractBeanFactory getBean 本文章向大家介绍Spring读书笔记——bean创建&#xff08;上&#xff09;&#xff0c;主要内容包括从getB…