在 EventHandler 中调用 this.StateHasChanged [英] Call this.StateHasChanged in EventHandler

查看:18
本文介绍了在 EventHandler 中调用 this.StateHasChanged的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下问题.我创建了一个事件并订阅了它,现在我希望 UI 在事件触发时发生变化.

 使用系统;使用 MintWebApp.Data;使用 MintWebApp.Models;使用 Microsoft.AspNetCore.Components;命名空间 WebApp.UI.Core{公共部分类 AppHeader{公共字符串状态 { 获取;放;}[注入]公共状态服务状态 { 获取;放;}事件处理程序<字符串>onStatusChanged= (sender, eventArgs) =>{//这里我得到错误,我无法访问这个和状态状态 = eventArgs;this.StateHasChanged();Console.WriteLine(eventArgs.PatientName);};protected override void OnInitialized() =>state.StatusHandler += onStatusChanged;}}

我收到此错误字段初始值设定项不能引用非静态字段、方法或属性AppHeader.patientContext"

关键字this"在当前上下文中不可用

如何订阅事件并更新 UI

解决方案

这需要以不同的方式处理,因为 EventHandler 类型在此处无法按预期工作.(至少不适合我)

首先,对于 EventArgs,请记住这是一种类型,因此您不能将它们分配给 Status 属性(您拥有的字符串)没有演员.这样做的方法是定义您自己的从 EventArgs 派生的参数类型,如下所示:

public class PatientEventArgs: EventArgs{公共字符串患者姓名 {get;放;}公共字符串 StatusValue {get;放;}}

接下来对于您需要使用的处理程序方法,将其设置为异步方法.我发现异步很重要,因此您可以使用 InvokeAsync 更远的地方,并在线程和调度程序不同意时避免异常,例如在其他窗口打开或其他用户在其他地方登录时,通过这个邮政:讨论线程与同步上下文

 private async void OnStatusChanged(object sender, EventArgs e) {//确保 args 是您期望的类型if(e.GetType() == typeof(PatientEventArgs))//转换为正确的Args类型以访问属性varpatientStatus = e as PatientEvendArgs;状态 = 患者状态.状态值;Console.Writeline(patientStatus.PatientName);/* 使用 InvokeAsync 方法和 await 以确保StateHasChanged 在这里正确运行而不会干扰另一个线程(打开窗口或其他用户)*/await InvokeAsync(() => StateHasChanged());}

接下来,重要对于您的场景,您将使用 Partial Class 声明碰壁,因为您需要实现 IDisposable 在组件拆卸时自己清理.相反,使用如下继承结构并使用 OnInitialized 和 Dispose 覆盖

AppHeader.razor.cs

public class AppHeaderBase : OwningComponentBase{//如上所述的 OnStatusChanged 方法protected override void OnInitialized()//也可以使用Async版本{//取消订阅一次以确保您只连接一次//阻止事件传播//如果这个组件没有被订阅,这不会做任何事情state.StatusHandler -= onStatusChanged;//订阅事件state.StatusHandler += onStatusChanged;}受保护的覆盖无效处置(布尔处置){//在拆卸时取消订阅,防止事件传播和内存泄漏state.StatusHandler -= onStatusChanged;}}

这利用了 OwningComponentBase 中的一些内置 Blazor 功能,并包含一个 Dispose 方法,同时为您更好地管理依赖注入.

进一步阅读 HERE(请注意,对于这个例子,我没有深入研究这个,因为它使用的是单例,但值得阅读以了解 DI 生命周期Blazor)

然后在您的 AppHeader.razor

....@inherits AppHeaderBase....

现在,当您从其他地方使用 StateService 中的事件处理程序时,使用您需要传递的值构建一个新的 PatientEventArgs 类型:

var newArgs = new PatientEventArgs(){患者姓名 = "SomeName",StatusValue = "SomeStatus";};

并根据需要在您的代码中传递它:

state.OnStatusChanged(this, newArgs);

或者直接从 Razor 语法:

这应该会根据需要多播您的事件,并且所有订阅者都应该接收并更新.

如果需要,这里有一个快速的工作演示,改编自我已经看过的另一个版本工作中.

I have the following problem. I created an event and subscribe to it, now I want that the UI changes when the Event triggers.

 using System;
 using MintWebApp.Data;
 using MintWebApp.Models;
 using Microsoft.AspNetCore.Components;
 namespace WebApp.UI.Core
 {
    public partial class AppHeader
    {
        public string status { get; set; }
        [Inject]
        public StateService state { get; set; }

        EventHandler<string> onStatusChanged= (sender, eventArgs) => {

            //Here i get the error, I can't access this and status
            status = eventArgs;
            this.StateHasChanged();
            Console.WriteLine(eventArgs.PatientName);
        };

        protected override void OnInitialized() => state.StatusHandler += onStatusChanged;
    }

}

I get this Error A field initializer cannot reference the non-static field, method, or property 'AppHeader.patientContext'

Keyword 'this' is not available in the current context

How can I subscripe to an event and update the UI

解决方案

This needs to be approached a bit differently as the EventHandler<T> type doesn't work as expected here. (At Least not for me)

First off, for the EventArgs, remember that this is a type, so you can't assign them to the Status property (which you have as a string) without a cast. The way to do this is to define your own arguments type that derives from EventArgs, something like this:

public class PatientEventArgs: EventArgs
{
    public string PatientName {get; set;}
    public string StatusValue {get; set;}
}

Next for the handler method that you need to use, set it up as an async method. I found that the async was important so you can use an InvokeAsync farther down and avoid an exception when the thread and dispatcher don't agree, as in other windows open or other users signed in elsewhere, through this post: Discussion on thread vs. Synchronization Context

 private async void OnStatusChanged(object sender, EventArgs e) {
    
    // Make sure the args are the type you are expecting        
    if(e.GetType() == typeof(PatientEventArgs))
        //Cast to the correct Args type to access properties
        var patientStatus = e as PatientEvendArgs;
        status = patientStatus.StatusValue;
        Console.Writeline(patientStatus.PatientName);

    /* Use InvokeAsync method with await to make sure 
    StateHasChanged runs correctly here without interfering with another
    thread (open window or other users) */
    await InvokeAsync(() => StateHasChanged());
}

Next, and important to your scenario, you will hit a wall with the Partial Class declaration as you have it since you need to implement IDisposable to clean up after yourself as the component tears down. Instead, use an inheritance structure as follows and use the OnInitialized and Dispose overrides

AppHeader.razor.cs

public class AppHeaderBase : OwningComponentBase
{

    // OnStatusChanged method as described above

    protected override void OnInitialized() //Can use the Async version as well
    {
        // Unsubscribe once to make sure you are only connected once
        // Prevents event propogation
        // If this component is not subscribed this doesn't do anything
        state.StatusHandler -= onStatusChanged;

        // Subscribe to the event
        state.StatusHandler += onStatusChanged;
    }

    protected override void Dispose(bool disposing)
    {
        // Unsubscribe on teardown, prevent event propogation and memory leaks
        state.StatusHandler -= onStatusChanged;
    } 

}

This takes advantage of some built in Blazor features in OwningComponentBase and includes a Dispose Method, while doing a much better job of managing your Dependency Injection for you.

Further reading HERE (Note that I didn't go too deep on this for this example as it's using a singleton, but worth the reading to understand DI lifetimes in Blazor)

And then in your AppHeader.razor

....

@inherits AppHeaderBase

....

Now when you use the event handler in the StateService from somewhere else, build up a new PatientEventArgs type with the values you need to pass:

var newArgs = new PatientEventArgs(){
    PatientName = "SomeName",
    StatusValue = "SomeStatus"  
};

And pass it in as needed in your code:

state.OnStatusChanged(this, newArgs);  

Or direct from Razor syntax:

<button @onclick="@(() => state.OnStatusChanged(this, new PatientEventArgs(){ PatientName = "SomeName", StatusValue = "SomeStatus"})">Sender Button</button>

This should multicast your event out as needed, and all subscribers should pick it up and update.

Here is a quick working demo if needed, adapted from another version of this I've been working on.

这篇关于在 EventHandler 中调用 this.StateHasChanged的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆