.NET不够美,用webVIew2集成web页面组件

补锅匠

Posted by 就是我啦 on November 22, 2023

.NET不够美,用webVIew2集成web页面组件

用.NET的Winform开发,发现界面居然很土很难看。和vue3下的element plus组件一比,简直没法看。想当初web开发刚出现的时候,界面也是又土又难看,没想到这么多年过去,桌面组件基本原地踏步,web组件确实如雨后春笋,越来越漂亮好看。

.Net也有一些专门美化过的组件,不过,基本都是收费的,而且美化后感觉依然不如开源的web组件。看来桌面组件这些年和web组件相比确实进步不大。web组件卷了十几年,其美观程度确实不可同日而语。所以桌面开发也出现了Electron这种怪胎。

引子

这一想法的缘起就是觉得桌面组件不好看。

image-20231122121427306

别的不说,仅仅是一个button,就让看管了web组件button的人感觉有点low。这是element plus的button:

image-20231122121642381

差距应该不是一点了。

虽然但是,还是尽量用.NET自己的组件比较好,搜了一下,大概有SunnyUI和hzhcontrols等几个开源项目可以用,但基本都是需要商用授权付费的,而且看上去也不是很好看的样子,遂起了嵌入web页面和web组件的念头,又好看又免费,正好还可以练习一下怎么在.NET中嵌入web页面。

试用结果

.NET中嵌入web页面也有很多种方法,包括:

  • WebBrowser
  • WebView
  • WebView2
  • Microsoft Edge WebView2*【强烈推荐】*
  • CefSharp
  • DotNetBrowser
  • 。。。

类似的还有好多。不过,尝试后发现很多都已经过时了,有的还是IE时代的产品。繁琐的试用对比后,发现还是WebView2比较好用。

WebView2 项目得天独厚,有微软操作系统win10以及win11的加持,最起码,生成的项目文件是很小的,我这边是3.6M,相对于CefSharp项目动辄100M的大小来讲,大大降低分发的大小,所以还是值得深入研究一下的。

开发需要的条件

  1. 运行时
  • WebView2 - Microsoft Edge Developer:https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section

img

通过控制面板,我们也能看到已经安装了这个运行时了。

没有的话,需要上边的那个地址下载安装。

具体地址: https://go.microsoft.com/fwlink/p/?LinkId=2124703

  1. Nuget包

需要引入以下Nuget包

Microsoft.Web.WebView2

复制

安装好之后,我这里默认是使用的WinFrom UI框架。

  • 案例参考: https://github.com/MicrosoftEdge/WebView2Samples

新建项目 (winfrom 作为参考)

img

如果没有出现WebView2可以重启一下项目就会有了

img

同时,为了方便看,我们把Dock属性选为Fill 全填充就好了

这个时候,我们添加一下的基础环境代码,就可以让页面启动了。

 public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Resize += new EventHandler(Form_Resize);
            webView21.CoreWebView2InitializationCompleted += WebView21_CoreWebView2InitializationCompleted;
            Initialize();
        }
         /// <summary>
         /// 实现自适应页面缩放
         /// </summary>
        private void Form_Resize(object sender, EventArgs e)
        {
            webView21.Size = ClientSize - new Size(webView21.Location);
        }
        /// <summary>
        /// webview 加载完毕
        /// </summary>
        private void WebView21_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
        {
            webView21.CoreWebView2.Navigate("https://www.baidu.com/");
        }
        /// <summary>
        /// WebView2初始化
        /// </summary>
        async void Initialize()
        {
            var result = await CoreWebView2Environment.CreateAsync(null, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache"), null);
            await webView21.EnsureCoreWebView2Async(result);
        }
    }

复制

img

这个页面可以自由的拉伸,十分的方便。

Web组件调用C#代码

最后还有一个大问题,就是web组件和C#代码的相互调用问题。毕竟web页面里面运行的是JS,winform的程序是C#,在我的例子里,需要点击web按钮组件的时候调用C#的程序。具体做法如下:

  1. 用vite+vue3+element plus, 新建一个web项目

    package.json

    {
      "name": "vitetest",
      "private": true,
      "version": "0.0.0",
      "type": "module",
      "scripts": {
        "dev": "vite",
        "build": "vue-tsc && vite build",
        "preview": "vite preview"
      },
      "dependencies": {
        "element-plus": "^2.4.2",
        "vue": "^3.3.8"
      },
      "devDependencies": {
        "@vitejs/plugin-vue": "^4.5.0",
        "typescript": "^5.2.2",
        "unplugin-auto-import": "^0.16.7",
        "unplugin-vue-components": "^0.25.2",
        "vite": "^5.0.0",
        "vue-tsc": "^1.8.22"
      }
    }
       
    
  2. App.vue中加入三个按钮, 编译运行

    App.vue

    <script>
    import { defineComponent } from 'vue'
    import { ElButton } from 'element-plus'
       
    export default defineComponent({
      components: {
        ElButton,
        //ElConfigProvider
      },
      setup() {
       
        const clickme = async () => {  
             
          alert("aaaa")
          window.chrome.webview.hostObjects.webBrowserObj.ClickMe1(); 
          // alert("bbb")
          // var hostObject = window.chrome.webview.hostObjects.customWebView2HostObject;
          // var result = await hostObject.TestCallFromJS();
          // alert(result)
        };  
        const clickme2 = async () => {  
             
          alert("bbbb")
          window.chrome.webview.hostObjects.webBrowserObj.ClickMe2(); 
          // alert("bbb")
          // var hostObject = window.chrome.webview.hostObjects.customWebView2HostObject;
          // var result = await hostObject.TestCallFromJS();
          // alert(result)
        };  
        const clickme3 = async () => {  
             
          alert("cccc")
          window.chrome.webview.hostObjects.webBrowserObj.ClickMe3(); 
          // alert("bbb")
          // var hostObject = window.chrome.webview.hostObjects.customWebView2HostObject;
          // var result = await hostObject.TestCallFromJS();
          // alert(result)
        };  
         
        return {  
          clickme,  
          clickme2,  
          clickme3, 
        };  
       
        // return {
        //   zIndex: 3000,
        //   size: 'large',
        // }
      },
    })
    </script>
    <template>
         
      <el-button type="primary" @click="clickme()" class="btn">MD5单磁盘扫描</el-button>
      <el-button type="success" @click="clickme2()" class="btn">单磁盘重复排查</el-button>
      <el-button type="danger" @click="clickme3()" class="btn">去扫描另一台新机器</el-button>
    </template>
       
    <style scoped>
    body {
        margin: 0;
        display: flex;
        place-items: center;
        min-width: 320px;
    }
       
    #app {
        max-width: 1280px;
        margin: 0 auto;
        padding: 0rem;
        padding-top: 0rem;
        padding-right: 2rem;
        padding-bottom: 0rem;
        padding-left: 2rem;
        text-align: center;
    }
    .btn{
      height:  50px;
      font-size:medium;
      margin-left: 20px;
      margin-right: 20px;
      }
    </style>
       
    

    image-20231122125824733

    最后,运行pnpm run build进行打包。

    将打包好的dist目录拷贝到.net项目的根目录下。

  3. visual studio中打开.net项目,在form中加入webView2组件

    image-20231122130059579

    在form的构造函数中加入以下部分:

            public FormMain()
            {
                InitializeComponent();
                InitializeAsync();
                /*
                string filePath = "app48.ico"; // 替换为你的.ico文件路径  
       
                try
                {
                    formIconBytes = File.ReadAllBytes(filePath);
                }
                catch (IOException e)
                {
                }
       
                this.Icon = new Icon(new MemoryStream(formIconBytes));
                */
            }
       
            async void InitializeAsync()
            {
                await webView21.EnsureCoreWebView2Async(null);
            }
       
       
            private void webView21_CoreWebView2InitializationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
            {
                if ((webView21 == null) || (webView21.CoreWebView2 == null))
                    Console.WriteLine("not ready");
                //webView21.NavigateToString(File.ReadAllText("index.html"));
                else
                {
                    webView21.CoreWebView2.AddHostObjectToScript("webBrowserObj", new ScriptCallbackObject());
       
                    //虚拟映射
                    webView21.CoreWebView2.SetVirtualHostNameToFolderMapping("html.sam", "./dist", CoreWebView2HostResourceAccessKind.Allow);
                    //导航
                    webView21.CoreWebView2.Navigate("https://html.sam/index.html");
                    webView21.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync("var webBrowserObj= window.chrome.webview.hostObjects.webBrowserObj;");
       
                    //MessageBox.Show("done");
                }
       
                //webView21.NavigateToString(File.ReadAllText("index.html"));
            }
       
    

    同时新增类ScriptCallbackObject.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    using WinFormsApp1;
       
    namespace FindDup4Disk
    {
        [ClassInterface(ClassInterfaceType.AutoDual)]
        [ComVisible(true)]
        /// <summary>
        /// 网页调用C#方法
        /// </summary>
        public class ScriptCallbackObject
        {
            public string UserName { get; set; } = "我是C#属性";
       
            public void ClickMe1()
            {
                FormMain currentForm = (FormMain)Form.ActiveForm;
                // 使用currentForm进行操作
       
                currentForm.button1_Click(null,null);
            }
            public void ClickMe2()
            {
                FormMain currentForm = (FormMain)Form.ActiveForm;
                // 使用currentForm进行操作
       
                currentForm.button5_Click(null, null);
            }
            public void ClickMe3()
            {
                FormMain currentForm = (FormMain)Form.ActiveForm;
                // 使用currentForm进行操作
       
                currentForm.button2_Click(null, null);
            }
       
            public void ShowMessageArg(string arg)
            {
                MessageBox.Show("【网页调用C#】:" + arg);
            }
       
            public string GetData(string arg)
            {
                return "【网页调用C#获取数据】;" + arg;
            }
       
            [System.Runtime.CompilerServices.IndexerName("Items")]
            public string this[int index]
            {
                get { return m_dictionary[index]; }
                set { m_dictionary[index] = value; }
            }
            private Dictionary<int, string> m_dictionary = new Dictionary<int, string>();
        }
    }
       
    
  4. 编译运行.net程序,成功调用

    image-20231122130612040

结论

虽然有点小麻烦,但总算是成功的。虽然没有尝试用C#调用JS,不过应该也是可以的。

最后,实验的成果就是这个.NET6 Runtime的小工具,免费开源:

https://github.com/shenyaojun/FindDup4Disk

https://gitee.com/shenyao/find-dup4-disk