Search code examples
vb.nettimercountdowncountdowntimer

The best way to make a simple and accurate stopwatch


I have a project that I have to do a Stopwatch. It works but it's not accurate compared to the Windows Timer. Also, I want to know if there is another efficient and simple way to do it, if so, how can I do it?. The interval of the Timer is 1.

Here's my code:

    Dim min As Integer = 30
    Dim seg As Integer = 59
    Dim mseg As Integer = 99
    Private Sub tmTimer_Tick(sender As Object, e As EventArgs) Handles tmTimer.Tick

        If lblStopWatch.Text = "00:00:00" Then
            lblStopWatch.ForeColor = Color.DarkGray
            tmTimer.Enabled = False
            min = 30
            seg = 59
            mseg = 99
        Else
            mseg -= 1
            If mseg < 0 Then
                mseg = 99
                seg -= 1
                If seg < 0 Then
                    seg = 59
                    min -= 1
                End If
            End If

            If min < 10 And seg < 10 And mseg < 10 Then
                lblStopWatch.Text = "0" + min.ToString + ":0" + seg.ToString + ":0" + mseg.ToString
            ElseIf min < 10 And seg < 10 Then
                lblStopWatch.Text = "0" + min.ToString + ":0" + seg.ToString + ":" + mseg.ToString
            ElseIf min < 10 And mseg < 10 Then
                lblStopWatch.Text = "0" + min.ToString + ":" + seg.ToString + ":0" + mseg.ToString
            ElseIf seg < 10 And mseg < 10 Then
                lblStopWatch.Text = min.ToString + ":0" + seg.ToString + ":0" + mseg.ToString
            Else
                If mseg < 10 Then
                    lblStopWatch.Text = min.ToString + ":" + seg.ToString + ":0" + mseg.ToString
                ElseIf seg < 10 Then
                    lblStopWatch.Text = min.ToString + ":0" + seg.ToString + ":" + mseg.ToString
                ElseIf min < 10 Then
                    lblStopWatch.Text = "0" + min.ToString + ":" + seg.ToString + ":" + mseg.ToString
                Else
                    lblStopWatch.Text = min.ToString + ":" + seg.ToString + ":" + mseg.ToString
                End If
            End If
        End If
    End Sub

And here's the Button that can start the Timer:

Private Sub btnAcept_Click(sender As Object, e As EventArgs) Handles btnAcept.Click

     lblCronometro.ForeColor = Color.RoyalBlue
        lblCronometro.Text = "30:00:00"
        tmCronometro.Enabled = True
        tmCronometro.Start()

End Sub

If the example is in C# or vb, I can get both. Thanks.


Solution

  • HINTS

    If you want to do it yourself instead of using the existing StopWatch class, another option would be to keep track of the time that the stopwatch started (start) and how long the stopwatch has been running (elapsed).

    With the start value, we can calculate the elapsed time in the timer's Tick event like:

    // elapsed is a TimeSpan object
    elapsed = DateTime.Now - start;
    

    If we add the ability to stop the stopwatch and then start it again (without resetting it), then we have to take that "downtime" into consideration. We can do this in our Start button click event by subtracting the elapsed time from the current time and using that value as the start time:

    // start is a DateTime object
    start = DateTime.Now.Subtract(elapsed);
    

    Then, when we want to display the elapsed time to the user, we can just use our elapsed field and format it like: hours:minutes:seconds.milliseconds using a custom format string.

    // I noticed you're doing a countdown timer, so we want to display the difference
    // between the original duration of 30 minutes and the elapsed time we've been running
    lblStopwatch.Text = (duration - elapsed).ToString("hh\\:mm\\:ss\\.ff");
    

    ANSWER

    If those hints aren't enough, here's a sample project using that strategy:

    public partial class Form1 : Form
    {
        private DateTime _startTime;
        private TimeSpan _elapsedTime;
    
        // The starting duration for the countdown timer
        private readonly TimeSpan _duration = TimeSpan.FromMinutes(30);
    
        public Form1()
        {
            InitializeComponent();
            ResetTimer();
        }
    
        private void ResetTimer()
        {
            _startTime = DateTime.Now;
            _elapsedTime = TimeSpan.Zero;
            UpdateStopwatchDisplay();
        }
    
        private void UpdateStopwatchDisplay()
        {
            // Display the current duration to the user
            lblStopwatch.ForeColor = timer1.Enabled ? Color.RoyalBlue : Color.DarkGray;
            lblStopwatch.Text = (_duration - _elapsedTime).ToString("hh\\:mm\\:ss\\.ff");
        }
    
        private void btnStart_Click(object sender, EventArgs e)
        {
            // Subtract 'elapsed' from 'now' to account for any stoppage time
            _startTime = DateTime.Now.Subtract(_elapsedTime);
            UpdateStopwatchDisplay();
            timer1.Start();
        }
    
        private void btnStop_Click(object sender, EventArgs e)
        {
            timer1.Stop();
            UpdateStopwatchDisplay();
        }
    
        private void btnReset_Click(object sender, EventArgs e)
        {
            ResetTimer();
        }
    
        private void timer1_Tick(object sender, EventArgs e)
        {
            // Update elapsed time
            _elapsedTime = DateTime.Now.Subtract(_startTime);
    
            // Stop the timer if we've reached our duration
            if (_elapsedTime >= _duration)
            {
                _elapsedTime = _duration;
                timer1.Stop();
            }
    
            UpdateStopwatchDisplay();
        }
    }