WinForm混合Blazor(中)

在上一篇中介绍了一下razor文件中,js与c#之间的相互调用,但WinForm和Blazor混合中,没有真正与WinForm进行交互,本篇来说明一下。

WinForm中混合Blazor是通过ServiceCollection来完成的,如果想WinForm和Blazor交互,可以通过向ServiceCollection注入一个中介服务来达到互相调用,大体思路是定义一个服务,这个服务里有方法和事件,方法被调用,触发订阅者。比如调用方是WinForm的话,订阅者就是对应js的方法了;如果调用方是js,那订阅事件的就是WinForm里的方法了。.

WinForm是不可能直接调用到JS的,主要通过IJSRuntime来调用js方法,同样,js也不能直接调WinForm,是通过js调razor中方法,razor方法再调用WinForm来实现,总体上就是razor中的C#层,是中间桥梁。razor中的 C#与WinForm就是通过注入ServiceCollection中的事件服务来互通协作(有点绕,多读几次)。

本例中定义了一个IEventHub接口和EventHub一个实现类完成封装。在这组服务中,既有CSharp的调用方法,又有JS调用方法,有CSharp事件,也有JS事件,代码如下:

IEventHub接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormsBlazor01
{
    public delegate Task<object> EventHubHandlerAsync<TEventArgs>(object sender, TEventArgs? eventArgs);
    public interface IEventHub
    {
        string? EventName { get; set; }
        event EventHubHandlerAsync<object?[]>? OnCallJSAsync;
        Task<object> CallJSAsync(string eventName, params object?[]? eventArgs);
        event EventHubHandlerAsync<object?[]>? OnCallCSharpAsync;
        Task<object> CallCSharpAsync(string eventName, params object?[]? eventArgs);
    }
}

EvnetHub类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormsBlazor01
{
    public class EventHub : IEventHub
    {
        public string? EventName { get; set; }

        public event EventHubHandlerAsync<object?[]>? OnCallJSAsync;

        public async Task<object> CallJSAsync(string eventName, params object?[]? eventArgs)
        {
            if (OnCallJSAsync != null)
            {
                EventName = eventName;
                return await OnCallJSAsync(this, eventArgs);               
            }
            return await Task.FromResult("");
        }

        public event EventHubHandlerAsync<object?[]>? OnCallCSharpAsync;

        public async Task<object> CallCSharpAsync(string eventName, params object?[]? eventArgs)
        {
            if (OnCallCSharpAsync != null)
            {
                EventName = eventName;
                return await OnCallCSharpAsync(this, eventArgs);          
            }
            return "";
        }
    }
}

再定义一个BlazorService类型,用来统一创建ServiceCollection:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.AspNetCore.Components.WebView.WindowsForms;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
namespace WinFormsBlazor01.Razors
{
    public class BlazorService
    {
        public static IEventHub CretaeBlazorService<IService, Service, RazorPage>(BlazorWebView blazorWebView)
            where IService : class
            where Service : class, IService
            where RazorPage : IComponent
        {
            var services = new ServiceCollection();
            services.AddWindowsFormsBlazorWebView();
            services.AddSingleton<IService, Service>();
            var eventHub = new EventHub();
            services.AddSingleton<IEventHub>(eventHub);
            blazorWebView.HostPage = "wwwroot\\index.html";
            blazorWebView.Services = services.BuildServiceProvider();

            blazorWebView.RootComponents.Add<RazorPage>("#app");
            return eventHub;
        }
        public static IEventHub CretaeBlazorService<RazorPage>(BlazorWebView blazorWebView) where RazorPage : IComponent
        {
            var services = new ServiceCollection();
            services.AddWindowsFormsBlazorWebView();   
            var eventHub = new EventHub();
            services.AddSingleton<IEventHub>(eventHub);
            blazorWebView.HostPage = "wwwroot\\index.html";
            blazorWebView.Services = services.BuildServiceProvider();

            blazorWebView.RootComponents.Add<RazorPage>("#app");
            return eventHub;
        }
    }
}

为了使很多个razor页面都有这个能力,还需要封装一个ComponentBase子类,来充当每个razor的父类,这样可以省略在每个razor中订单事件,代码如下:

using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormsBlazor01.Razors
{
    public abstract class EventComponent : ComponentBase, IDisposable
    {
        protected DotNetObjectReference<EventComponent>? dotNetHelper;
        protected override async Task OnInitializedAsync()
        {
            IEventHub? eventHub = null;
            IJSRuntime? js = null;
            foreach (var pro in this.GetType().GetProperties(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance))
            {
                if (typeof(IEventHub).IsInstanceOfType(pro.GetValue(this, new object[0])))
                {
                    eventHub = pro.GetValue(this, new object[0]) as IEventHub;
                }
                if (typeof(IJSRuntime).IsInstanceOfType(pro.GetValue(this, new object[0])))
                {
                    js = pro.GetValue(this, new object[0]) as IJSRuntime;
                }
            }
            if (eventHub != null && js != null)
            {
                eventHub.OnCallJSAsync += CallAsync;
                async Task<object> CallAsync(object sender, object?[]? eventArgs)
                {
                    var eventhub = sender as EventHub;
                    return await js.InvokeAsync<object>(eventhub?.EventName!, eventArgs);
                }
            }
            if (js != null)
            {
                dotNetHelper = DotNetObjectReference.Create(this);
                await js.InvokeVoidAsync("CallHelpers.DotNetHelper", dotNetHelper);
            }
            base.OnInitialized();
        }
        public void Dispose()
        {
            dotNetHelper?.Dispose();
        }
    }
}

有了razor page在的父类了,怎么继承呢?定义一个_Imports.razor,来当所有razor的共公引用,这里需经继承EventComponent,同时引用注入IEventHub和IJSRuntime两个接口的子对像:

@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop;
@inherits EventComponent
@inject IEventHub eventHub
@inject IJSRuntime js

所有基础工作准备好了,接下来试验一把,在index.html(这里不清楚查看前一篇《WinForm混合Blazor(上)》)添加js代码:

function showtitle(title) {
    document.getElementById("title").innerText = "JS接到WinForm数据:" + title
    return title
}

class CallHelpers {
    static dotNetHelper;

    static DotNetHelper(value) {
        CallHelpers.dotNetHelper = value;
    }

    static async callForm2(name) {

        const msg = await CallHelpers.dotNetHelper.invokeMethodAsync('CallForm2', name);
        alert(`JS接到WinForm的返回值: "${msg}"`);
    }
}
window.CallHelpers = CallHelpers;

razor页面文件如下:

@using Microsoft.AspNetCore.Components.Web
<div class="row">
    <h2>我是HTML窗体</h2>
</div>
<div class="row">
    <div class="col-12">
        <div class="input-group mb-3">
            <input type="text" class="form-control" id="FindName" placeholder="请输入信息" @bind="InputName" aria-describedby="button-addon2">

            <button class="btn btn-outline-secondary" type="button" @onclick="CallForm1" id="button-addon2">razorC#触发WinForm事件</button>

            <button class="btn btn-outline-secondary" type="button" onclick="CallHelpers.callForm2(document.getElementById('FindName').value)" id="button-addon2">JS触发WinForm事件</button>
        </div>
    </div>
</div>
<div class="row">
    <h3 id="title"></h3>
</div>
@code {
    private string? InputName="ABCDEFG123456";
    #region 方法一,用@bind调razor中C#方法,再调用WinForm中方法
    async void CallForm1()
    {
        var result = await eventHub.CallCSharpAsync("clientclick", InputName);
        MessageBox.Show("razor中C#接到的返回结果:" + result.ToString());
    }
    #endregion

    #region 方法二,通过js方法调用razor中C#方法,再调用WinForm中方法
    /// <summary>
    /// 这个方法是js中调过来的
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    [JSInvokable]
    public async Task<object> CallForm2(string name)
    {
        var result = await eventHub.CallCSharpAsync("clientclick",  name );
        return result;
    }
    #endregion
}

WinForm中定义如下:

WinForm窗体Designer文件

namespace WinFormsBlazor01
{
    partial class AddForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.addBlazorWebView = new Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView();
            this.panel1 = new System.Windows.Forms.Panel();
            this.txtNo = new System.Windows.Forms.TextBox();
            this.labBackMessage = new System.Windows.Forms.Label();
            this.label1 = new System.Windows.Forms.Label();
            this.labMessage = new System.Windows.Forms.Label();
            this.button1 = new System.Windows.Forms.Button();
            this.panel1.SuspendLayout();
            this.SuspendLayout();
            // 
            // addBlazorWebView
            // 
            this.addBlazorWebView.Dock = System.Windows.Forms.DockStyle.Fill;
            this.addBlazorWebView.Location = new System.Drawing.Point(0, 176);
            this.addBlazorWebView.Name = "addBlazorWebView";
            this.addBlazorWebView.Size = new System.Drawing.Size(967, 414);
            this.addBlazorWebView.TabIndex = 1;
            this.addBlazorWebView.Text = "queryBlazorWebView";
            // 
            // panel1
            // 
            this.panel1.Controls.Add(this.txtNo);
            this.panel1.Controls.Add(this.labBackMessage);
            this.panel1.Controls.Add(this.label1);
            this.panel1.Controls.Add(this.labMessage);
            this.panel1.Controls.Add(this.button1);
            this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
            this.panel1.Location = new System.Drawing.Point(0, 0);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(967, 176);
            this.panel1.TabIndex = 2;
            // 
            // txtNo
            // 
            this.txtNo.Location = new System.Drawing.Point(423, 39);
            this.txtNo.Name = "txtNo";
            this.txtNo.Size = new System.Drawing.Size(362, 23);
            this.txtNo.TabIndex = 6;
            // 
            // labBackMessage
            // 
            this.labBackMessage.AutoSize = true;
            this.labBackMessage.Location = new System.Drawing.Point(423, 111);
            this.labBackMessage.Name = "labBackMessage";
            this.labBackMessage.Size = new System.Drawing.Size(73, 17);
            this.labBackMessage.TabIndex = 5;
            this.labBackMessage.Text = "-------------";
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Font = new System.Drawing.Font("Microsoft YaHei UI", 16F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
            this.label1.Location = new System.Drawing.Point(12, 9);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(198, 30);
            this.label1.TabIndex = 4;
            this.label1.Text = "我是WinForm窗体";
            // 
            // labMessage
            // 
            this.labMessage.AutoSize = true;
            this.labMessage.ForeColor = System.Drawing.Color.Red;
            this.labMessage.Location = new System.Drawing.Point(38, 91);
            this.labMessage.Name = "labMessage";
            this.labMessage.Size = new System.Drawing.Size(116, 17);
            this.labMessage.TabIndex = 3;
            this.labMessage.Text = "============";
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(817, 82);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(121, 34);
            this.button1.TabIndex = 2;
            this.button1.Text = "触发Html事件";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // AddForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(967, 590);
            this.Controls.Add(this.addBlazorWebView);
            this.Controls.Add(this.panel1);
            this.Name = "AddForm";
            this.Text = "AddForm";
            this.panel1.ResumeLayout(false);
            this.panel1.PerformLayout();
            this.ResumeLayout(false);
        }

        #endregion
        private Microsoft.AspNetCore.Components.WebView.WindowsForms.BlazorWebView addBlazorWebView;
        private Panel panel1;
        private Button button1;
        private Label labMessage;
        private Label label1;
        private Label labBackMessage;
        private TextBox txtNo;
    }
}

窗体cs文件

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using WinFormsBlazor01.Razors;
namespace WinFormsBlazor01
{
    public partial class AddForm : Form
    {
        IEventHub _eventHub;
        public AddForm()
        {
            InitializeComponent();
            _eventHub = BlazorService.CretaeBlazorService<Add>(addBlazorWebView);
            _eventHub.OnCallCSharpAsync += EventHub_OnCallCSharpAsync;
            txtNo.Text = Guid.NewGuid().ToString("N").ToUpper();
        }

        private async Task<object> EventHub_OnCallCSharpAsync(object sender, object?[]? eventArgs)
        {
            var eventHub = sender as EventHub;
            if (eventHub?.EventName == "clientclick" && eventArgs != null && eventArgs.Length > 0)
            {
                labMessage.Text = "JS事件传过来的参数:" + eventArgs?[0]?.ToString()!;
            }
            return await Task.FromResult(eventArgs?[0]?.ToString()!);
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            var result = await _eventHub.CallJSAsync("showtitle", txtNo.Text);
            labBackMessage.Text = "WinForm调用JS返回值:" + result.ToString();
        }
    }
}

查看结果:

JS调用WinForm,用@bind方式

WinForm混合Blazor(中)

JS调用WinForm,用js方法调用

WinForm混合Blazor(中)

WinForm方法调用JS

WinForm混合Blazor(中)

附代码:https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/Blazor/WinFormsBlazor01