在计时器结束时从 parent 更改 child 值

change child value from parent on timer elapse

我有一个简单的 parent-child 组件:

parent :

<div class="container-fluid">
    <div>
        <ChildComponent RandomNumber="@RandomNumberX" ></ChildComponent>
    </div>
</div>
@code {

public double RandomNumberX = 0.1;

private Timer timer = new Timer();

protected override void OnInitialized()
{
    timer.Interval = 3000;
    timer.Elapsed -= Set;
    timer.Elapsed += Set;
    timer.Start();
}

private void Set(object sender, ElapsedEventArgs e)
{
    var rng = new Random();

    var x = rng.Next(500, 600);
    RandomNumberX = x;
}}

Child:

<svg height="100" width="100" style="position:absolute;  border-color: black; ">
    <g>
        <circle cx="50%" cy="25" r="5" stroke="red" stroke-width="2" fill="red" />
        <text x="50%" y="50" text-anchor="middle">@RandomNumber</text>
    </g>
</svg>
@code {
[Parameter]
public double RandomNumber { get; set; }

protected override void OnParametersSet()
{
        
}
}

当 运行 应用程序“RandomNumber”显示 0.1 并且在 parent 组件中不会更改时,它会在计时器结束时更改。

TimerElapsed 不是 'normal' Blazor 生命周期事件。所以它不会自动触发重新渲染。 (例如,ButtonClick 事件)。

因此您需要调用 StatehasChanged,并且为了满足它在另一个线程上的可能性,您需要 InvokeAsync。

private async void Set(object sender, ElapsedEventArgs e)
{
    var rng = new Random();

    var x = rng.Next(500, 600);
    RandomNumberX = x;
    await InvokeAsync(StatehasChnaged);
}

您通常应该避免 async void 但这正是它创建的目的。

旁注:初始化期间的timer.Elapsed -= Set;只是解决方案的一半。

在页面顶部使用 @implements IDisposable 并添加

public void Dispose()
{
    timer.Elapsed -= Set;
}

我不久前制作了一个 SVG 时钟的动画:

注意:dispose 的使用以及我如何通过检查第二次更改将计算减少到仅在需要时进行。 Clock.razor

<div class="d-flex flex-column align-items-center">
    <div class="time-sm">@timeZone.Id</div>
    <svg width="@Size" height="@Size" viewBox="0 0 1000 1000">
        <circle cx="@center" cy="@center" r="@radius" fill="@FaceColor" />
        <ClockHand Angle="hourAngle" Color="@HourColor" Width="50" Length="0.9" />
        <ClockHand Angle="minuteAngle" Color="@MinuteColor" Width="30" Length="0.95" />
        <ClockHand Angle="secondAngle" Color="@SecondColor" Width="20" Length="1" />
    </svg>
    <div class="time-sm">@currentSecond.DateTime.ToShortTimeString()</div>
    <div class="time-sm">@currentSecond.DateTime.ToString("dddd")</div>
</div>

Clock.razor.cs

public partial class Clock : ComponentBase, IDisposable
{
    internal const int radius = 500;
    internal const int size = 1000;
    internal const int center = size / 2;
    private double secondAngle = 0;
    private double minuteAngle = 0;
    private double hourAngle = 0;
    private TimeZoneInfo timeZone;
    private DateTimeOffset currentSecond;
    private readonly System.Timers.Timer timer = new();

    [Parameter]
    public int Size { get; set; } = 50;

    [Parameter]
    public string FaceColor { get; set; } = "#f5f5f5";

    [Parameter]
    public string HourColor { get; set; } = "blue";

    [Parameter]
    public string MinuteColor { get; set; } = "green";

    [Parameter]
    public string SecondColor { get; set; } = "red";

    [Parameter]
    public string TimeZone { get; set; }

    protected override void OnInitialized()
    {

        if (string.IsNullOrWhiteSpace(TimeZone) is true)
        {
            timeZone = TimeZoneInfo.Local;
        }
        else
        {
            timeZone = TimeZoneInfo.FindSystemTimeZoneById(TimeZone);
        }

        timer.Interval = 100;
        timer.Elapsed += Timer_Elapsed;
        UpdateClock();
        timer.Start();
    }
    public static DateTime GmtToPacific(DateTime dateTime)
    {
        return TimeZoneInfo.ConvertTimeFromUtc(dateTime,
            TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
    }
    private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        UpdateClock();
        InvokeAsync(StateHasChanged);
    }

    private void UpdateClock()
    {
        const double radiansPer60 = 360 / 60 * Math.PI / 180;
        const double radiansPer12 = 360 / 12 * Math.PI / 180;

        var currentTime = TimeZoneInfo.ConvertTime(DateTimeOffset.Now, timeZone);
        var roundedSencond = new DateTimeOffset(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second, default);

        if (roundedSencond != currentSecond)
        {

            currentSecond = roundedSencond;

            var seconds = currentTime.Second;
            var minutes = currentTime.Minute;
            var hours = currentTime.Hour % 12;

            secondAngle = seconds * radiansPer60;
            minuteAngle = minutes * radiansPer60 + secondAngle / 60;
            hourAngle = hours * radiansPer12 + minuteAngle / 12;
        }
    }

    public void Dispose()
    {
        if (timer is not null)
        {
            timer.Dispose();
        }
    }
}

ClockHand.razor

<line x1="500" y1="500" x2="@X" y2="@Y" style="stroke:@Color;stroke-width:@Width" />

ClockHand.razor.cs

public partial class ClockHand : ComponentBase
{
    [Parameter]
    public double Angle { get; set; }

    [Parameter]
    public double Length { get; set; } = 1;

    [Parameter]
    public string Color { get; set; } = "black";

    [Parameter]
    public int Width { get; set; }

    double X => Math.Sin(Angle) * Clock.radius * Length + Clock.center;
    double Y => Math.Cos(Angle) * -Clock.radius * Length + Clock.center;
}

Useage

<Clock TimeZone="Australia/Sydney" />

<Clock />