C# Reset a countdown timer-DispatcherTimer- in win

2019-08-04 15:57发布

问题:

I'm a C# newbie_and in programming in general_ and Ι'm trying to build a math quiz app with a countdown timer.

I generate an equation each time the user clicks the start button and I give him a max 60 seconds to answer. The user answers -whether his answer is wrong or right doesn't matter_ and can he/she can click again for a new equation. So I want the timer to reset each time the user is shown a new random equation. So far I've only managed to reset this when the 60sec timespan elapses but even that is not working properly, sometimes it displays 59 or 58 secs instead of 60.

So far reading other questions has't helped me much and the timer confuses me. I also accept suggestions to make my code simpler and more elegant.

Here is my code:

EquationView.xaml.cs

public sealed partial class EquationView : Page
    {
        DispatcherTimer timer = new DispatcherTimer();
        int tick = 60;
        int result;

        public EquationView()
        {
            this.NavigationCacheMode = NavigationCacheMode.Enabled;
            this.InitializeComponent();
        }

        private void startButton_Click(object sender, RoutedEventArgs e)
        {
            // Once clicked then disabled
            startButton.IsEnabled = false;

            // Enable buttons required for answering 
            resultTextBox.IsEnabled = true;
            submitButton.IsEnabled = true;

            var viewModel = App.equation.GenerateEquation();
            this.DataContext = viewModel;
            result = App.equation.GetResult(viewModel);

            timer.Interval = new TimeSpan(0, 0, 0, 1);
            //timer.Tick += new EventHandler(timer_Tick);
            timer.Tick += timer_Tick;
            timer.Start();
            DateTime startTime = DateTime.Now;

            // Reset message label
            if (message.Text.Length > 0)
            {
                message.Text = "";
            }

            // Reset result text box
            if (resultTextBox.Text.Length > 0)
            {
                resultTextBox.Text = "";
            }
        }

        private void timer_Tick(object sender, object e)
        {
            Countdown.Text = tick + " second(s) ";
            if (tick > 0)
                tick--;
            else
            {
                Countdown.Text = "Times Up";
                timer.Stop();
                submitButton.IsEnabled = false;
                resultTextBox.IsEnabled = false;
                startButton.IsEnabled = true;
                tick = 60;
            }

        }

        private void submitButton_Click(object sender, RoutedEventArgs e)
        {
            timer.Stop();
            submitButton.IsEnabled = false;
            resultTextBox.IsEnabled = false;

            if (System.Text.RegularExpressions.Regex.IsMatch(resultTextBox.Text, "[^0-9]"))
            {
                MessageDialog msgDialog = new MessageDialog("Please enter only numbers.");
                msgDialog.ShowAsync();

                resultTextBox.Text.Remove(resultTextBox.Text.Length - 1);

                //Reset buttons to answer again
                submitButton.IsEnabled = true;
                resultTextBox.IsEnabled = true;
                timer.Start();
            }
            else
            {
                try
                {
                    int userinput = Int32.Parse(resultTextBox.Text);

                    if (userinput == result)
                    {
                        message.Text = "Bingo!";
                        App.player.UpdateScore();
                        startButton.IsEnabled = true;
                    }
                    else
                    {
                        message.Text = "Wrong, sorry...";  
                        startButton.IsEnabled = true;
                    }
                }
                catch (Exception ex)
                {
                    MessageDialog msgDialog = new MessageDialog(ex.Message);
                    msgDialog.ShowAsync();
                    submitButton.IsEnabled = true;
                    resultTextBox.IsEnabled = true;
                    timer.Start();
                }


            }
        }

回答1:

It seems to me that you have at least two significant problems here. One is that your timer will likely give the user more than 60 seconds, due to the inaccuracy in the Windows thread scheduler (i.e. each tick will occur at slightly more than 1 second intervals). The other (and more relevant to your question) is that you don't reset the tick value to 60 except when the timer has elapsed.

For the latter issue, it would be better to simply reset your countdown value when you start the timer, rather than trying to remember everywhere that you stop it.

To fix that and the first issue, get rid of the tick field altogether and change your code to look more like this:

    static readonly TimeSpan duration = TimeSpan.FromSeconds(60);
    System.Diagnostics.Stopwatch sw;

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        // Once clicked then disabled
        startButton.IsEnabled = false;

        // Enable buttons required for answering 
        resultTextBox.IsEnabled = true;
        submitButton.IsEnabled = true;

        var viewModel = App.equation.GenerateEquation();
        this.DataContext = viewModel;
        result = App.equation.GetResult(viewModel);

        sw = System.Diagnostics.Stopwatch.StartNew();
        timer.Interval = new TimeSpan(0, 0, 0, 1);
        timer.Tick += timer_Tick;
        timer.Start();

        // Reset message label
        if (message.Text.Length > 0)
        {
            message.Text = "";
        }

        // Reset result text box
        if (resultTextBox.Text.Length > 0)
        {
            resultTextBox.Text = "";
        }
    }

    private void timer_Tick(object sender, object e)
    {
        if (sw.Elapsed < duration)
        {
            Countdown.Text = (int)(duration - sw.Elapsed).TotalSeconds + " second(s) ";
        }
        else
        {
            Countdown.Text = "Times Up";
            timer.Stop();
            submitButton.IsEnabled = false;
            resultTextBox.IsEnabled = false;
            startButton.IsEnabled = true;
        }
    }

This way, it won't matter exactly when the tick event happens, the code will still correctly compute the actual time remaining and use that for the display and to tell whether the time is up.