0

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 
        }
  • You pass `motorTimer_1` into `IndividualTestStart()` as the parameter `dispatcherTimer`. Then you replace the parameter's value with a newly created `DispatcherTimer`, which you initialize and start. `motorTimer_1` is never started, and is never a reference to the `DispatcherTimer` which is actually running. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 15:38
  • Ah, that makes sense. However, I removed the replacement of that parameter's value & now the timer won't tick at all. Working on that now. – Schreiberito May 01 '19 at 16:45
  • Stop and think it through. How are you now initializing the DispatcherTimer `motorTimer_1`? Are you giving it an interval, a priority, a delegate, a dispatcher? Where? How? Are you creating a new DispatcherTimer just as before, but just throwing it away instead of assigning it to the parameter? Let's see the new code. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 16:53
  • ```` timeSpan = TimeSpan.FromSeconds(10); { timeRemaining.Text = timeSpan.ToString("c"); //Sets the text in the textbox to the time remaining in the timer if (timeSpan == TimeSpan.Zero) { dispatcherTimer.Stop(); } timeSpan = timeSpan.Add(TimeSpan.FromSeconds(-1)); }; dispatcherTimer.Interval = new TimeSpan(0, 0, 1); dispatcherTimer.Start(); //Starts the timer } ```` – Schreiberito May 01 '19 at 17:02
  • Please add it to the question, so I can read it. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 17:02
  • Well, the formatting didn't work, I may just have to post it in the question to make it readable. I've got the interval set, but no priority, delegate, or dispatcher. Looks like I'll need to dig into that more. – Schreiberito May 01 '19 at 17:03
  • You can edit this question. You don't need to dig into anything. Just set the properties on the same DispatcherTimer you're actually running, and call Stop() on that one instead of a different one. \ – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 17:03
  • I tried adding the properties in, on the top: DispatcherTimer cooktopTimer_1 = new DispatcherTimer(new TimeSpan(0, 0, 1), DispatcherPriority.Normal, delegate { },Application.Current.Dispatcher ); Still just sticks at 10 though. – Schreiberito May 01 '19 at 17:13
  • Well, if you won't do as I ask, unfortunately I can't help you. Naturally nobody can debug a program when all they see is tiny fragments of code with no context. Good luck with your problem. One thing though, is that I would advise you to use a delegate that has some code in it, if you want anything to happen when the delegate is called. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 17:14
  • I'm not really clear on what you're asking. I'm learning as I go. I have the rest of the code added at the bottom of the question now. based on what I know, I have to give it those parameters when I first initialize it. However, the problem is I can't give it the parameters outside of the method, ass the delegate is contained within the method. – Schreiberito May 01 '19 at 17:48
  • So give it what it needs when you create it. Why do you feel a need to create it wrong, and then pass it into this method? What you need to do: When somebody tries to start it, call a method that checks if it exists yet. If not, create it properly and *assign* the new instance to motorTimer_1. Once you’re sure it exists, start it. Are you going to have multiple similar timers (motorTimer_2 etc)? – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 17:54
  • I am creating 8 different instances of timers, exactly the same, except for the parameters I'm passing into that method (The button that starts/stops it, the timer name, the timespan,& the textboxes it us updating). The reason I'm creating it "wrong" is that this is the first time I've worked with timers in C# & all the tutorials I've found are extremely basic for one timer entirely contained in one method. The way I understand it, I can't create the timer within the method, because I have another method (Stop All) that needs to access them as well. – Schreiberito May 01 '19 at 18:07
  • So create them and store references somewhere. See my answer. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 18:09

1 Answers1

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.

  • I'll start working on getting this implemented into my code, thanks! In the mean-time, this raises a question. I was under the impression that if you created a timer in a method, you couldn't access that timer in another method. That's why I created the timers at the beginning of my code outside of the methods. I assume that isn't the case & I can access the timer anywhere, as long I refer to it by its name? – Schreiberito May 01 '19 at 18:15
  • @Schreiberito Please see corrected code in `motorOnBtn_1_Click`, there was a serious omission in the initial version, which broke everything. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 18:21
  • 1
    Thanks for all the help. Sounds like I'll need to learn more about classes & bindings to really wrap my head around what you're pointing me towards. I'll work on that this evening & keep this to refer back to once I understand better! – Schreiberito May 01 '19 at 18:43
  • The general term for all that collections-and-datatemplates stuff is "MVVM", or the "Model/View/Viewmodel pattern". The learning curve is steep but it's immensely powerful, and a lot of people here can help you with snags. Have fun. – 15ee8f99-57ff-4f92-890c-b56153 May 01 '19 at 18:45