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方式
JS调用WinForm,用js方法调用
WinForm方法调用JS
附代码:
https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/Blazor/WinFormsBlazor01
《asp.net core精要讲解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core项目实战》 https://ke.qq.com/course/291868
《基于.net core微服务》 https://ke.qq.com/course/299524