Home > Software engineering >  change child value from parent on timer elapse
change child value from parent on timer elapse

Time:01-02

I have a simple parent-child component :

parent :

<div >
    <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()
{
        
}
}

when running app "RandomNumber" show 0.1 and does not change while in parent component it changes on timer elapsed.

CodePudding user response:

I animated a SVG clock a little while back that does this:

Note: The use of dispose and how I reduce the calculations to only when needed by checking for a second change. Clock.razor

<div >
    <div >@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 >@currentSecond.DateTime.ToShortTimeString()</div>
    <div >@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" />

or

<Clock />

enter image description here

CodePudding user response:

TimerElapsed is not a 'normal' Blazor lifecycle event. So it won't automatically trigger a re-render. (A ButtonClick event would, for example).

So you need to call StatehasChanged, and to cater for the possibility it's on another Thread you need to InvokeAsync that.

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

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

You should normally avoid async void but this is exactly the suituation it was created for.

Side note: timer.Elapsed -= Set; during Initialization is only half a solution.

Use @implements IDisposable on the top of your page and add

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