Ensure a method only fires once when an event is triggered multiple times in Winforms newer method in .NET 4.5 - vb.net

So essentially I have wired up three text boxes to do a smart filter and want to let a user do a multi filter. The only problem was that it was firing too frequently and I want to have it fire after a delay. The event for 'TextChanged' is wired up to basically run and I have a simplified example of what I want:
I have a simple Winforms UI with two text boxes: "txtWait" and "txtTest". In the front end code the properties are default and the text are:
txtWait.Text = 1000
txtTest.Text = "Test Text I have here to look at"
A way to test this is to just hit the backspace a few times and wait. I would want only the last text to show once. I just got this part to work but the resetting it not occurring as I would expect. I would expect a person could hit backspace, backspace, (only a half a second had passed), backspace(clock resets and new wait begins).
And my code behind is:
Public Class DelayBeforeAction
Private _loaded As Boolean = False
Private _sw As Stopwatch = New Stopwatch()
Public Sub New()
InitializeComponent()
_loaded = True
End Sub
Private Sub txtTest_TextChanged(sender As Object, e As EventArgs) Handles txtTest.TextChanged
If _loaded Then
_sw.Start()
DelayExecute(Sub() If _sw.ElapsedMilliseconds > CInt(txtWait.Text) Then _sw.Reset() : MessageBox.Show(txtTest.Text) Else _sw.Reset(), CInt(txtWait.Text))
End If
End Sub
Private Async Sub DelayExecute(action As Action, timeout As Integer)
Await Task.Delay(timeout)
action()
End Sub
End Class

Concretely in your case, your first txtTest_TextChanged starts a stopwatch. Your second txtTest_TextChanged calls _sw.Start() again, which has no effect on a running stopwatch:
Starting a Stopwatch that is already running does not change the timer state or reset the elapsed time properties.
When the first txtTest_TextChanged's continuation runs, the stopwatch's elapsed time is expected to be greater than a second: it was started more than a second ago, and since then, all that happened is that other attempts were made to start the same stopwatch. Nothing was reset.
That said, using a stopwatch here is inherently unreliable and I do not recommend continuing down this path. You cannot be sure exactly when your continuation runs.
Instead, do not measure whether your continuation should probably be cancelled, track whether it was cancelled.
The most direct way in your particular case would be to increment a counter in txtTest_TextChanged. If the counter has not been changed by the time the continuation is executed, you know txtTest_TextChanged hasn't been called a second time.
A more general way is to use the CancellationTokenSource class. Most task-based methods, including Task.Delay, have overloads accepting CancellationToken instances. You can indicate a request for cancellation through CancellationTokenSource.Cancel.
Although you do not need it in this case, in general, you can also call CancellationToken.ThrowIfCancellationRequested explicitly in specific locations during long-running operations that would not otherwise be aborted.

Related

Label Text not updating even when using Refresh, Invalidate, and DoEvents

My code is designed to be a control system for a 2-axis motion system. I have 2 drives that each output a count of their steps. I can read the device, update a property, and update the text field of a label. However, it does not update the form. When I use a message box, I can display the text value being correct, but nothing updates the label.
I'm happy to try any suggestions, but I've been fighting this for about 16 hours and I'm at my wits end - as evidenced by the clear overkill/terrible coding that is shown in the code. I can't understand why it's not updating.
Additionally, a manual button with all versions seen below to refresh a form doesn't update the control.
Direction, recommendations?
Private Sub PositionChanged(ByVal sender As Object, ByVal e As EventArgs)
If TraverseController.InvokeRequired Then
TraverseController.Invoke(
New EventHandler(Of EventArgs)(AddressOf PositionChanged), sender, e)
Return
End If
'RaiseEvent PropertyChanged(TraverseController, New System.ComponentModel.PropertyChangedEventArgs("Position"))
MessageBox.Show(TraverseController.lblLinearDrivePosDisp.Text)
TraverseController.lblLinearDrivePosDisp.Text = CStr(_position)
Application.DoEvents()
TraverseController.lblLinearDrivePosDisp.ResetBindings()
TraverseController.GBDrivePositionDisp.Refresh()
TraverseController.lblLinearDrivePosDisp.Refresh()
TraverseController.Refresh()
TraverseController.Invalidate()
TraverseController.Update()
Application.DoEvents()
MessageBox.Show(TraverseController.lblLinearDrivePosDisp.Text)
End Sub
Assumption: TraverseController is form's class name.
This looks like a VB default form instance issue. It is apparent that you are trying to properly marshal control interaction back to the UI thread by using checking TraverseController.InvokeRequired. However, due to the way these default instance are created, TraverseController.InvokeRequired is creating a new instance of TraverseController on the secondary thread and all subsequent code is modifying that instance and not the one created on the UI thread.
One way to deal with this is to pass a synchronizing control instance to the class where PositionChanged changed method is defined and check that control's InvokeRequired method instead of TraverseController.InvokeRequired. If the containing class is itself a UI control, then use that class instance (Me.InvokeRequired).

In Excel vba, how to achieve timer function

What I thought would be quick excel vba, doesn't seem to be quick anymore. Basically, i want to loop through a set of questions within a given time. when the given time has expired or all questions completed, exit the loop (close the form and return). BTW, would like to show a timer countdown on the form as well. is it possible to achieve it?
I can get a form to show questions with answer options but not sure how to add the time criteria.
As far I know, this is not an ease task to achieve. Briefly speaking, one of the possible ways to do it is:
1. write your own class module for this program
2. create a form with questions, through the form constructor (Form_Initialize) create a new instance of your class (from point 1.) and link it to the form through variables, in addition your form needs to have variables to pass the information supplied by user at a later stage (your questions) to the object behind for storing
3. when your class is instantiated, the object will have a piece of code that contains a timer, and this timer will fire events, let's say, every second to refresh your form (to show how much time is left for answering questions), see UserForm.Repaint method. The questions that have been answered so far would be re-inserted into the form on repaint (info should be stored in your class object, then re-used to update the form through timer events).
Check this link as a starting point:
https://msdn.microsoft.com/en-us/library/office/gg264327.aspx
or search for: RaiseEvent Statement in Excel Help (in VB Editor).
To see how to create vba classes:
http://www.cimaware.com/resources/article_39.html
In general, you need to search for information on how to:
create class modules,
separate object model (instantiated class) from the form,
pass information between the form and the model,
make the model to repaint your form at regular intervals (firing events)
I think Application.OnTime method may can help.
Scheduling Events With OnTime And Windows Timers
---updated---
As requested in comment section, I include the essential part on how the function works
'variable to keep time interval the timer run again
Public RunWhen As Double
'main function that kick off the exam
Public Sub startExam()
'a label display how much time left, says start with 60 seconds
DisplayTime.Caption = "60"
'call a function that load and display question on screen
'assume call LoadQuestion again when user provided quiz answer, not implemented
LoadQuestion (1)
'start the
call Timer()
End Sub
'the timer function repeat itself
Sub Timer()
'set the next run time for Timer function
RunWhen = Now + TimeSerial(0, 0, 1)
'set the schedule
Application.OnTime EarliestTime:=RunWhen, Procedure:="Timer", Schedule:=True
'update the time left on screen
UserForm1.DisplayTime.Caption = UserForm1.DisplayTime.Caption - 1
'if the time deduced to 0, stop the schedule and alert user
If UserForm1.DisplayTime.Caption = "0" Then
Application.OnTime EarliestTime:=RunWhen, Procedure:="Timer", chedule:=False
MsgBox "TimesUp"
End If
End Sub

Console app timer to call web methods every x minutes

I am coding in VB.Net, VS 2008.
I wrote a console app that consumes 2 web methods from a web site application. I need to enhance this console app so that it launches the web methods continuously, perhaps every x minutes (during business hours), but never before the last invocation has terminated, whose duration may vary, depending on how many accounts there are to process.
Originally, I scheduled the application using Task Scheduler, but I think this doesn't prevent two invocations at the same time.
Although I have seen many posts on using timers, I haven't found exactly what I need.
So far I have:
Dim aTimer As New System.Timers.Timer()
AddHandler aTimer.Elapsed, AddressOf TriggerWebMethods
' Set the Interval to 10 minutes:
aTimer.Interval = 1000 * 60 * 10 '(1 second * 60 = 1 minute * 10 = 10 minutes)
aTimer.Enabled = True
aTimer.AutoReset = True
When should Timer.Elapsed be used vs. Timer.Tick?
What is the difference between Timer.Enabled vs Timer.Start, and should I be selecting just one?
I would like the 2nd web method to kick off when the first one is done.
I'd like to keep this as simple as possible. Thank you for all help.
If you are dealing with a System.Timers.Timer, then you'd only have the Elapsed event available. If a System.Windows.Forms.Timer, then you'd use the Tick event. You're not writing a WinForms app so you would be using the System.Timers.Timer.
Personally, I would only use the Enabled property to see if the timer has been started. I wouldn't use it to start or stop it. Using the Start() or Stop() method makes it very clear what's happening to the timer.
If your web methods execute synchronously, you could just call them one after the other in your TriggerWebMethods() method. The second will not be called until the first completes.
Sub TriggerWebMethods(source As Object, e As ElapsedEventArgs)
FirstWebMethod()
SecondWebMethod()
End Sub
If asynchronously, you'd have to register a callback on the first web method to execute the second when it completes. In VB, I believe you can use the second directly as the callback, depending on how you make the asynchronous call. (Sorry, my VB is very rusty now so might not be 100% correct syntax)
Sub FirstWebMethod()
' ...
End Sub
Sub SecondWebMethod()
' ...
End Sub
Sub TriggerWebMethods(source As Object, e As ElapsedEventArgs)
Dim first As Action = AddressOf FirstWebMethod
first.BeginInvoke(AddressOf SecondWebMethod, first)
End Sub
Just to add a little to Jeff M's answer. The Timer.Elapsed Event has the following note.
If the SynchronizingObject property is null, the Elapsed event is
raised on a ThreadPool thread. If the
processing of the Elapsed event lasts
longer than Interval, the event might
be raised again on another ThreadPool
thread. In this situation, the event
handler should be reentrant.
Since you're in a Console app you can either hand roll you own SynchronizingObject or you can set the AutoReset to False, and change your TriggerWebMethods to have a start at the end. You may even want to offset the interval to take into consideration the amount of processing time.
e.g.
Dim start As Date = Now
'do stuff
Dim ts As TimeSpan = Now - start
Dim i As Integer = (1000 * 60 * 10) - ts.TotalMilliseconds
Dim aTimer As Timers
aTimer.Interval = i

Event handler not removing itself?

At the beginning of a VB .NET function I remove an event handler and add it again at the end of the function because the function's code would trigger those events and I do not want them to trigger for the duration of the function. This usually works, but I have run into a few situations where the event still gets called even though I have already removed it. Sometimes removing it twice at the beginning of the function fixes it, but other times no matter how many times I remove it, it still fires. Any ideas on what could be causing this?
Edit
The code is in a Form that has a virtual mode datagridview. I want to run some operations that will trigger the CellValueNeeded event for the datagridview without that event being fired (because it will interfere).
Public Sub DoEventRaisingStuff()
RemoveHandler grid.CellValueNeeded, AddressOf grid_CellValueNeeded
'Do things that would trigger CellValueNeeded
AddHandler grid.CellValueNeeded, AddressOf grid_CellValueNeeded
End Sub
Removing the handler multiple times does not prevent the event from firing, so it doesn't seem to be added multiple times somewhere else by accident.
Is there a way to find out what event handlers are active?
If the event handling code is being called then one of two things is happening:
You aren't removing the event handler.
You are adding the event handler multiple times. This is the more usual case.
In the past the only way I've been able to spot 2. is to find all the places where the event handler is added (hopefully only one or two) and put break points on those lines. I've then run the application under the debugger and found that it breaks more times than I expect. I use the call stack to work out why - it's always me putting the add code in the wrong place (on a button press rather than on form instantiation for example).
You can do the same with the removal code. Count the number of times each break point is hit and if they're not the same work back up the call stack to see if you can work out why.
Use class scoped flag in the function and check the flag in the event handler.
i.e.:
Private RunFunction as Boolean = False
...
Private Sub MyEvent(e as system.eventargs) handles myObject.Method
If RunFunction Then
...
End If
End Sub
...
Private Sub MyFunction()
RunFunction = False
...
RunFunction = True
End Sub

How to limit CPU usage in a while loop

How do you limit the CPU of a while loop?
In this case, the code which is inside the while loop:
Private Sub wait(ByVal time)
Dim sw As New Stopwatch
sw.Start()
Do While sw.ElapsedMilliseconds < time And StillOpen = True
Application.DoEvents()
Loop
sw.Stop()
End Sub
But now, here is the issue. This loop is allowing the while loop to run every second, once a second, and the wait sub is causing this delay, as it should.
How can I limit the CPU that this is taking up? For some reason, my task manager says it is taking 50 CPUs to run this simple task, yet it should probably take no more than 1 or 2. Though the manager says it is taking that much CPU, my computer speed is not being affected at all, which is odd considering it is a two-year-old laptop.
I don't want any users to freak out about it, but knowing how people are these days....
Anyway, the language is vb.net. Can someone please help me?
Thanks!
EDIT: To clarify, that code is not inside the while loop itself, but a call for the subroutine is, i.e. wait(1000)
Use a timer event !!! Nearly no cpu effort.
You could always perform some kind of sleep between iterations of the loop...
I'm not familiar with VB.NET but a duration of 100-200ms will probably be more than enough to drop the CPU usage.
Eg:
Do while (...)
Application.blah();
System.Threading.Thread.Sleep(150);
End
Edit After some research, I think the function you want is: System.Threading.Thread.Sleep()
Your code is executing Application.DoEvents() constantly in the while loop, for the time duration specified in your time parameter. This will consume one core of your CPU, which is why you're seeing 50% processor usage (you have a dual-core processor, correct?). This is an ugly way to wait. You could instead call Thread.Sleep(), passing it the number of milliseconds you'd like your thread to wait.
If you'd like your application to stay responsive, you might also spin off a timer, and block the UI from any action until the timer triggers. Something like (lightly tested):
// constructor or designer code
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Tick += new EventHandler(timer_Tick);
void Wait(int interval)
{
timer.Interval = interval;
timer.Start();
BlockUIOperations(); // implement yourself
}
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
EnableUIOperations(); // implement yourself
}
Here's my attempt at a translation into VB:
'' Add a Timer object to the form named "Timer".
'' Hook its Tick event to Timer_Tick
Private Sub Wait(ByVal interval As Integer)
Timer.Interval = interval
Timer.Start()
BlockUIOperations() '' implement yourself
End Sub
Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer.Tick
Timer.Stop()
EnableUIOperations() '' implement yourself
End Sub
Well, the CPU is always running at 100% when it's running, so the only practical way to limit the CPU usage is to run bursts or loop and sleeping in between.
Laptop CPUs usually have some SpeedStep technology or equievalent that will slow down the CPU when it's not working hard, but it's not reasonable to assume that your application would have access to control that, at least not directly. You might be able to affect it indirectly by measuring the CPU usage and adjust the length of the work and sleep cycles to get the desired result.
If you don't mind blocking the current thread, you could use a WaitHandle.
Public Sub Wait(ByVal ms As Integer)
Using wh As New ManualResetEvent(False)
wh.WaitOne(ms)
End Using
End Sub
Sub Main()
Console.WriteLine("Hello World!")
Wait(5000)
Console.WriteLine("Good-Bye!")
End Sub
Of course, something more complex can be constructed depending on what you are trying to accomplish.
This is perfect as a VB.net sleep replacement. Now my console app is NOT reported as non responsive since I have no sleep commands!
Just add Imports System.Threading above your module and place this just above your sub main
Public Sub Wait(ByVal ms As Integer)
Using wh As New ManualResetEvent(False)
wh.WaitOne(ms)
End Using
End Sub
Then, in your sub main, use
wait(100)
to pause your app for 100 miliseconds.
Have fun
You should take note of if you are doing this in the main UI Thread or a thread you have spun off.
For Threads the easiest way is to just Thread.Sleep(x miliseconds)
On the main UI thread I tend to use a DoEvents function in vb.net and vb6 like this
Public Sub TimeKiller(byval secondstowait as integer)
dim tmptime as datetime = datetime.now
do while datetime.now < dateadd("s",secondstowait,tmptime)
Application.Doevents
end while
End Sub
On the question of CPU usage I look at it like this.... if you make just a hard loop that like
while true
end while
I would expect to see very high cpu usage over 50% because the UI thread is hard blocking on this.... in most cases the windows system will limit the cpu usage of any given program so that its threads dont block the entire system.
The DoEvents ensure that windows message pumps fire correct and respond to correct. It also ensures that the garbage collector fires on time.
Also if you have other threads spun up off of your UI.Thread your UI.Thread can respond to events fired from these other threads....
In such cases where your calling form controls from other threads and do form.InvokeRequired routines will be able to respond correctly.
Also The only time you should be hard looping on the MainUI thread is when it is in response to some user activity and you need to put waits in for the user to see progress of something....
If it is some kind of automated process that is always running... look to moving it to another thread.
Or if its something that runs periodically on a timer or a time that kicks off a thread.
Somebody please tell me if I am wrong on these assumptions....
Not sure about the Using wh As New ManualResetEvent(False) wh.WaitOne(ms) as I have never heard of that and have no idea what that does.