Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
352 views
in Technique[技术] by (71.8m points)

c# - Call this.StateHasChanged in EventHandler

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...