29

I'm new to blazor C# and trying to make a simple countdown timer website. My website consist of:

  • Text to display the timer
  • Start and stop button
  • Buttons to set the timer

I'm having a problem in the buttons to set the timer. When i click on it, it won't set the timer display and i got an error Argument 2: cannot convert from 'void' to 'Microsoft.AspNetCore.Components.EventCallback'. I search on Youtube for the EventCallback topic but the problem is, my component is not seperated while in the video the example code got seperated components linked together. Here's the code.
Index.razor

@page "/"
@inherits FrontEnd.Components.Timer

<h1>Timer</h1>

<p class="timer">@Hours.ToString("00"):@Minutes.ToString("00"):@Seconds.ToString("00")</p>
@foreach (var timer in timerCollections)
{
    // Here's the problem
    <button @onclick="SetTimer(timer.Id)">@timer.Hours.ToString("00"):@timer.Minutes.ToString("00"):@timer.Seconds.ToString("00")</button>
}
<br/>
<button @onclick="StartTimer" disabled=@StartButtonIsDisabled>Start</button>
<button @onclick="StopTimer" disabled=@StopButtonIsDisabled>Stop</button>

Timer.cs

using System;
using System.Timers;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;

namespace FrontEnd.Components
{
    public class TimerStructure
    {
        public int Id { get; set; }
        public int Hours { get; set; }
        public int Minutes { get; set; }
        public int Seconds { get; set; }      
    }
    public class Timer : ComponentBase
    {
        public List<TimerStructure> timerCollections = new List<TimerStructure>(){
            new TimerStructure(){ Id = 1, Hours = 0, Minutes = 30, Seconds = 0 },
            new TimerStructure(){ Id = 2, Hours = 1, Minutes = 0, Seconds = 0 }
        };

        public int Index { get; private set; }
        public int Hours { get; set; } = 0;
        public int Minutes { get; set; } = 0;
        public int Seconds { get; set; } = 0;
        public bool StopButtonIsDisabled { get; set; } = true;
        public bool StartButtonIsDisabled { get; set; } = false;
        private static System.Timers.Timer aTimer;
        // and this is the function related to the problem
        public void SetTimer(int value)
        {
            this.Index = value - 1;
            Hours = timerCollections[Index].Hours;
            Minutes = timerCollections[Index].Minutes;
            Seconds = timerCollections[Index].Seconds;
        }
        public void StopTimer()
        {
            aTimer.Stop();
            aTimer.Dispose();

            StopButtonIsDisabled = true;
            StartButtonIsDisabled = false;

            Console.WriteLine($"{Hours}:{Minutes}:{Seconds}");
        }
        public void StartTimer() 
        {
            aTimer = new System.Timers.Timer(1000);
            aTimer.Elapsed += CountDownTimer;
            aTimer.Start(); 

            StopButtonIsDisabled = false;   
            StartButtonIsDisabled = true;
        }
        public void CountDownTimer(Object source, ElapsedEventArgs e)
        {
            if(Seconds == 0 && Minutes > 0)
            {
                Minutes -= 1;
                Seconds = 59;
            } else if (Minutes == 0 && Seconds == 0 && Hours > 0)
            {
                Hours -= 1;
                Minutes = 59;
                Seconds = 59;
            } else if (Hours == 0 && Minutes == 0 && Seconds == 0)
            {
                aTimer.Stop();
                aTimer.Dispose();

                StopButtonIsDisabled = true;
                StartButtonIsDisabled = false;
            } else
            {
                Seconds -= 1;
            }
            InvokeAsync(StateHasChanged);
        }
    }
}
TheNoobProgrammer
  • 1,013
  • 4
  • 10
  • 21

1 Answers1

74

Try: <button @onclick="() => SetTimer(timer.Id)">

Brian Parker
  • 11,946
  • 2
  • 31
  • 41
  • 1
    It's got something to do with the fact that you're looping, and therefore creating multiple callbacks. I'm going to need to read this (https://www.telerik.com/blogs/how-to-pass-arguments-to-your-onclick-functions-blazor) again but I think it contains your explanation ;-) – Stuart Helwig Jul 29 '21 at 11:07
  • 11
    onclick takes either zero parameters or 1 parameter of type MouseEventArgs. You can't add your own, so you'll need to use an anonymous function to call your actual method with the captured variable. – Lee McPherson Oct 04 '21 at 23:10
  • I think @LeeMcPherson 's comment is the real answer, as it explains what's going on. Note also this issue doesn't just happen inside a loop: I got this problem with a similar button `@onclick` method taking an argument, where the button wasn't inside a loop. – Simon Elms Jan 07 '23 at 11:39
  • 1
    See also Henk's answer to a similar Stackoverflow question: https://stackoverflow.com/a/67758865/216440 – Simon Elms Jan 07 '23 at 11:44