"Counter" on form quits counting when form loses focus

Zardlord

Member
Joined
Mar 14, 2009
Messages
16
Programming Experience
1-3
Most of my experience in Visual Basic programming is in VBA. I'm currently trying to build a VB.NET Windows application that basically consists of a form that has one button and one label. When the button is clicked, the label should serve as a "counter" that counts from 0 to infinity until the user closes the window with the "x" button.

I do this by putting a loop with a sleep statement in the onclick sub. The loop iterates the integer that is displayed in the label and does a "Me.Refresh".

The problem is when the window loses focus. When it loses focus (when I click off of it), the counter stops. Even when I click back on it, the counter doesn't resume. If anyone can tell me what I'm doing wrong, I'll be grateful.
 
The problem with a timer is although its set to go off every second, its actually off slightly. Picture a bad watch that lags a few seconds behind every minute. Additionally you will coding in order to calculate hours/minutes/seconds etc plus coding to format its display.

Lastly there is much that is incorrect and bad coding practice in how you are trying to keep a counter. If you had Option Explicit turned on in Visual Studio, the coding you have would not even compile. You can not add a number plus a string without converting it to its proper datatype and likewise you can not display a number in a label unless first converting it to a string.

Try this

01) With your form level variables, declare a stopwatch object
Dim m_swTimer As New Stopwatch

02) In your Button1_Click event under Timer1.Start start your stopwatch
Timer1.Start
m_swTimer.Start

03) In your Timer1_Tick event add :

lblCounter.Text = String.Format("{0:00}:{1:00}:{2:00}.{3:000}", _
m_swTimer.Elapsed.Hours, _
m_swTimer.Elapsed.Minutes, _
m_swTimer.Elapsed.Seconds, _
m_swTimer.Elapsed.Milliseconds)
 
Tom,

Thanks so much for all the help. I guess by using the stopwatch, the lag of the timer will get "picked up" every 20 or 30 seconds or so.
 
Wow, I just tested that and you were right, there is a ton of lag, it's clearly visible. I'd say that there is more than 5 seconds of lag for every minute that goes by.

So, the tick event of the timer is required to set the label's value to the latest elapsed time of the stopwatch and do a Me.Refresh on the form. Is there a way do this same thing, but for every second that elapses under the actual stopwatch?
 
As an attempt to answer my own question, I think if I want to have what amounts to a visible stop watch that counts upward accurately, I'm going to have to create a custom class that uses events and event handling. Am I right about this?
 
Is there a way do this same thing, but for every second that elapses under the actual stopwatch?
Use a Timer with Interval 1000 to display the elapsed time every second. The actual elapsed time will never be wrong.
do a Me.Refresh on the form
Why??
 
Use a Timer with Interval 1000 to display the elapsed time every second. The actual elapsed time will never be wrong.

This is what I'm already doing. I realize that for each 1000 milliseconds that elapses under the Timer, that the current time elapsed as measured by the Stopwatch will be displayed. However, this DOES produce visible skipping of numbers. For example, you might see the time jump from 25 seconds to 27 seconds.

Also, as far as doing a Refresh on the form, I've found that this is the only way to get the new value of the Label displayed. Am I wrong about this?
 
Also, as far as doing a Refresh on the form, I've found that this is the only way to get the new value of the Label displayed. Am I wrong about this?
Yes. Or if that is what you must do, then you are doing something else wrong that is blocking the UI events.
you might see the time jump from 25 seconds to 27 seconds.
So set Interval to 500, how wrong can that be?
 
Now I have a question about event handling and multithreading. I know that when an event gets raised and the sub that handles the event is called, that sub is on its own "thread" or whatever and on that thread it cannot access the attributes of the class that it was called from. If I try to do this I get a runtime error about cross-threading or something. However, I WOULD like to change the label of the form from within an event handler sub. For example:

VB.NET:
Public Class SendForUpdate
'===================================================
' THIS CLASS IS A FORM 
'===================================================
	Private objWatcher As FileSystemWatcher

	Private Sub bttnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bttnSend.Click

		objWatcher = New FileSystemWatcher("C:\Temp\WatchDir", "*.*")

		objWatcher.NotifyFilter = NotifyFilters.FileName
		AddHandler objWatcher.Created, AddressOf OnNewFileCreated
		objWatcher.EnableRaisingEvents = True

		bttnSend.Enabled = False

	End Sub

	Sub OnNewFileCreated(ByVal objSource As Object, ByVal objEvent As FileSystemEventArgs)

	    '===================================================
	    ' HERE I WANT TO BE ABLE TO MANIPULATE LABELS THAT
	    ' ARE ON THE FORM
	    '===================================================
		
	End Sub

End Class

So I'm thinking that I need to add arguments to the OnNewFileCreated sub, where those arguments will be references to the various labels that I want to manipulate from within Sub OnNewFileCreated. Is this how this is done?
 
I got some help on that last one. Here is the solution:

VB.NET:
Public Class SendForUpdate
'===================================================
' THIS CLASS IS A FORM 
'===================================================
    Private objWatcher As FileSystemWatcher

    Delegate Sub SetLabelAsDoneCallback()

    Private Sub bttnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles bttnSend.Click

        objWatcher = New FileSystemWatcher("C:\Temp\WatchDir", "*.*")

        objWatcher.NotifyFilter = NotifyFilters.FileName
        AddHandler objWatcher.Created, AddressOf OnNewFileCreated
        objWatcher.EnableRaisingEvents = True

        bttnSend.Enabled = False

    End Sub

    Sub OnNewFileCreated(ByVal objSource As Object, ByVal objEvent As FileSystemEventArgs)

        Me.Invoke(New SetLabelAsDoneCallback(AddressOf SetLabelAsDone))
		
    End Sub

    Private Sub SetLabelAsDone()

        Me.lblCounter.Text = "DONE"
        Me.Refresh()

    End Sub

End Class
 
Tom said:
The timer is set for one second but to be honest the timer isnt that accurate. You might be much better using the stopwatch elapsed methods to show time past in it.
Tom said:
The problem with a timer is although its set to go off every second, its actually off slightly. Picture a bad watch that lags a few seconds behind every minute.
I read between the lines here that you may be thinking the Timer can/should be used to measure time, but that it's timing is off. Timers are not used to measure time, they are used to trigger reoccurring events at the interval given. "A Timer is used to raise an event at user-defined intervals" They are more than accurate for their purpose - which is not to measure time. "The Windows Forms Timer component is single-threaded, and is limited to an accuracy of 55 milliseconds. If you require a multithreaded timer with greater accuracy, use the Timer class in the System.Timers namespace." So the Timers.Timer will therefore callback at greater accuracy than 55ms, but it's purpose is still the same as the Forms.Timer. That said, it is also clear you know how to measure time (timeA-timeB / StopWatch), but I thought it was appropriate to comment your statements here, the Timers misconception is also common.
(Quotes courtesy of the help system.)
Zardlord said:
I'd say that there is more than 5 seconds of lag for every minute that goes by.
That depends on how long your UI code is blocking the UI thread.
 
I read between the lines here that you may be thinking the Timer can/should be used to measure time, but that it's timing is off. Timers are not used to measure time, they are used to trigger reoccurring events at the interval given.

No my point was the opposite; the timer should not be used to measure time only to fire the event to compare the elapsed time (by a stopwatch) if constant progress was needed to be displayed.
 
Back
Top