Schreiberito
Schreiberito

Reputation: 29

Stopping DispatcherTimer with a button click

When I click my stop button, my timer is still counting down, even though I tell it to stop.

My current relevant code:

I'm naming the timers here, as I need to access them for a stop/start all button as well.

namespace Row_Interface
{
    public partial class MainWindow : Window
    {
        //Declare the timers here, so the stop all button can access them as well
        DispatcherTimer motorTimer_1 = new DispatcherTimer();
        TimeSpan motorCycleTime_1 = TimeSpan.FromSeconds(0);

When I click the on button, the IndividualTestStart method is called & passed the relevant parameters:

public void motorOnBtn_1_Click(object sender, RoutedEventArgs e)
        {
            IndividualTestStart(motorOnBtn_1, motorOffBtn_1, motorTimer_1, motorCycleTime_1, timeUntilmotorCycle_1, motorTestCycles_1);
        }

When I click the off button, I'm wanting to stop that timer so the cycle never finishes:

        private void motorOffBtn_1_Click(object sender, RoutedEventArgs e)
        {
            motorTimer_1.Stop();
            motorOnBtn_1.IsEnabled = true; //Enables the start test button
            motorOffBtn_1.IsEnabled = false; //Disables the stop test button

        }

This is called when I click start. I'll eventually have something similar for the stop button, but I'm taking things one step at a time:

private void IndividualTestStart(Button startButton, Button stopButton, DispatcherTimer dispatcherTimer, TimeSpan timeSpan, TextBox timeRemaining, TextBox cycleCount)
        {
            stopButton.IsEnabled = true; //Enables the stop button

            //Set the time to run. This will be set from the database eventually.
            timeSpan = TimeSpan.FromSeconds(10);

            //Set up the new timer. Updated every second.
            dispatcherTimer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate
            {
                timeRemaining.Text = timeSpan.ToString("c"); //Sets the text in the textbox to the time remaining in the timer
                startButton.IsEnabled = false; //Disables the start test button once the test is started
                if (timeSpan == TimeSpan.Zero) //Checks to seee if the time has run out
                {
                    dispatcherTimer.Stop(); //Stops the timer once the time has run out
                    startButton.IsEnabled = true; //Enables the start test button
                    int initialCycleCount = 0;
                    initialCycleCount++;
                    cycleCount.Text = initialCycleCount.ToString();
                    stopButton.IsEnabled = false;//Disables the stop button

                }
                timeSpan = timeSpan.Add(TimeSpan.FromSeconds(-1)); //Subtracts one second each time the timer "ticks"
            }, Application.Current.Dispatcher);  //runs within the UI thread

            dispatcherTimer.Start(); //Starts the timer 
        }
}

When I click the stop button, I expect the timer in the textbox to stop counting down. However, it just keeps on ticking. When I click stop, the start button is re-enabled, so I know that it's triggering the code in the event handler. But it isn't stopping the timer.

Not starting a new timer now. New code:

        public void motorOnBtn_1_Click(object sender, RoutedEventArgs e)
        {
            IndividualTestStart(motorOnBtn_1, motorOffBtn_1, motorTimer_1, motorCycleTime_1, timeUntilmotorCycle_1, motorTestCycles_1);
        }

        private void IndividualTestStart(Button startButton, Button stopButton, DispatcherTimer dispatcherTimer, TimeSpan timeSpan, TextBox timeRemaining, TextBox cycleCount)
        {
            stopButton.IsEnabled = true; //Enables the stop button

            //Set the time to run. This will be set from the database eventually.
            timeSpan = TimeSpan.FromSeconds(10);

            {
                timeRemaining.Text = timeSpan.ToString("c"); //Sets the text in the textbox to the time remaining in the timer
                startButton.IsEnabled = false; //Disables the start test button once the test is started
                if (timeSpan == TimeSpan.Zero) //Checks to seee if the time has run out
                {
                    dispatcherTimer.Stop(); //Stops the timer once the time has run out
                    startButton.IsEnabled = true; //Enables the start test button
                    int initialCycleCount = 0;
                    initialCycleCount++;
                    cycleCount.Text = initialCycleCount.ToString();
                    stopButton.IsEnabled = false;//Disables the stop button

                }
                timeSpan = timeSpan.Add(TimeSpan.FromSeconds(-1)); //Subtracts one second each time the timer "ticks"
            };  //runs within the UI thread

            dispatcherTimer.Start(); //Starts the timer 
        }

Upvotes: 0

Views: 859

Answers (1)

The problem in your code is that you initialize motorTimer_1 with a DispatcherTimer that doesn't do anything, then you pass motorTimer_1 in as the dispatcherTimer parameter, and then you replace the value of the parameter with a newly created, different DispatcherTimer.

The new timer works fine, but when you call stop on motorTimer_1, nothing happens, because that's not the one that's running. You could simply assign the new DispatcherTimer directly to motorTimer_1 in IndividualTestStart(), but you've gone to great trouble to parameterize everything in IndividualTestStart() so it can work with different DispatcherTimers.

Instead, here's what we'll do: There is no reason to pass in a DispatcherTimer. IndividualTestStart() must create the DispatcherTimer in order to initialize it. OK, let's run with that. It will create a new one and return it.

private DispatcherTimer IndividualTestStart(Button startButton, Button stopButton, 
    TimeSpan timeSpan, TextBox timeRemaining, TextBox cycleCount)
{
    stopButton.IsEnabled = true; //Enables the stop button

    //Set the time to run. This will be set from the database eventually.
    timeSpan = TimeSpan.FromSeconds(10);

    //  Set up the new timer. Updated every second.
    var dispatcherTimer = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate
    {
        timeRemaining.Text = timeSpan.ToString("c"); //Sets the text in the textbox to the time remaining in the timer
        startButton.IsEnabled = false; //Disables the start test button once the test is started
        if (timeSpan == TimeSpan.Zero) //Checks to seee if the time has run out
        {
            dispatcherTimer.Stop(); //Stops the timer once the time has run out
            startButton.IsEnabled = true; //Enables the start test button
            int initialCycleCount = 0;
            initialCycleCount++;
            cycleCount.Text = initialCycleCount.ToString();
            stopButton.IsEnabled = false;//Disables the stop button

        }
        timeSpan = timeSpan.Add(TimeSpan.FromSeconds(-1)); //Subtracts one second each time the timer "ticks"
    }, Application.Current.Dispatcher);  //runs within the UI thread

    dispatcherTimer.Start(); //Starts the timer 

    return dispatcherTimer;
}

public void motorOnBtn_1_Click(object sender, RoutedEventArgs e)
{
    if (motorTimer_1 == null)
    {
        //  Create/initialize a new timer and assign it to motorTimer_1
        motorTimer_1 = IndividualTestStart(motorOnBtn_1, motorOffBtn_1, 
            motorCycleTime_1, timeUntilmotorCycle_1, motorTestCycles_1);
    }
    else
    {
        //  It's already there, just start it. 
        motorTimer_1.Start();
    }
}

Since this is WPF, you'll want to write a viewmodel class TimerThing (think of a better name) that owns a DispatcherTimer, two commands to start it and stop it, and a public bool property that indicates whether it's running or not. IndividualTestStart() should be a method of that class. The parent viewmodel will have have an ObservableCollection<TimerThing> containing an arbitrary number of TimerThings, which will be displayed in an ItemsControl with an ItemTemplate that creates buttons bound to the Start and Stop commands. The above code will look very different, since none of the C# code will know anything about buttons: Instead, the buttons in the item template XAML will be enabled/disabled by bindings.

Upvotes: 1

Related Questions