Have an IF statement constantly check - vb.net

Is there anyway to have an IF statement constantly checking? I'm working with a List and I need to check if the list count exceeds a variable. I've since noticed the easiest way to make the program run smoothly is to have an IF statement checking constantly while the program is running.

The best approach for this would be to change from 'List' to 'BindingList'. This is an event enabled list which will fire events when the list changes:
Private WithEvents mList As New System.ComponentModel.BindingList(Of String)
Public Sub Main()
mList.Add("An Item")
End Sub
Private Sub mList_AddingNew(sender As Object, e As System.ComponentModel.AddingNewEventArgs) Handles mList.AddingNew
If mList.Count > 100 Then
MessageBox.Show("Threshold exceeded")
End If
End Sub
Alernatively you could start a thread / timer that polls this, however you'll have to watch out for synchronization issues.

Related

How to properly interact with UI in threading in VB.NET

I'm new to VB.NET threading
As for simple testing I tried the following, which I need to smoothly fill a listbox with values.
But it does not work as I expect, it hangs the interface. Please let me know what I'm doing wrong here.
Thank you.
Imports System.Threading
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim Thr As Threading.Thread
Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
'Thr.SetApartmentState(ApartmentState.STA)
Thr.IsBackground = True
Thr.Start()
End Sub
Private Delegate Sub DoStuffDelegate()
Private Sub tprocess()
Dim i As Integer
For i = 0 To 20000
If Me.InvokeRequired Then
Me.Invoke(New DoStuffDelegate(AddressOf tprocess))
Else
ListBox1.Items.Add(i)
End If
Next
End Sub
End Class
When you write code to create a thread then you always have to worry about the kind of bugs that threading can cause. They are very hard to diagnose, the only decent way to address them is to know they exist and to write the code carefully so you know how to avoid them.
The most common threading bugs are threading races, deadlock and firehose bugs. You have the 1st and the 3rd bug in your code. You are complaining about the 3rd. Very quickly: the threading race bug is using Me.InvokeRequired. You have no guarantee that it is still true when the Me.Invoke() statement executes. This goes wrong when the user closes the window while your thread is still running. When you try to fix this problem you'll get to see what the 2nd bug looks like. But you are not there yet.
The firehose bug is the Me.Invoke() call. Very fast, takes less than a microsecond of work for the worker thread, you do it 20000 times at a very high rate. It is however another thread that must actually do the work of adding the item, your UI thread. That is not fast, it not only has to add the item but it also needs to repaint the control. Many microseconds.
While this goes on, your UI thread is burning 100% core, trying to keep up with the relentless rate of invoke requests. Working as hard as it can to add items to the listbox. Something has to give, while it is doing this it is not taking care of the lower priority jobs it has to do. Painting and responding to user input. In effect, your UI looks completely frozen. You can't see it paint anymore and trying to, say, close the window doesn't work. It isn't actually dead, it is hard at work.
Takes a while, probably a few handful of seconds, give or take. Until the worker thread finishes its for() loop and stops slamming the UI thread with invoke requests. And everything turns back to normal.
A firehose bug like this is pretty fundamental. The only way to fix it is to call Invoke() less often or at a lower rate. Note how putting Thread.Sleep(50) after the Invoke() call instantly fixes it. But of course that slows down your worker thread a lot. You call Invoke() less often by using AddRange() instead of Add(), adding (say) 1000 items at a time. Which is the proper fix but now it becomes fairly pointless to still try to update the listbox from the worker thread. Might as well do it with a single AddRange() call. The quickest way.
Try changing:
Thr = New Threading.Thread(New Threading.ThreadStart(AddressOf tprocess))
to this:
Thr = New Threading.Thread(AddressOf tprocess)
ThreadStart will start that thread immediately
I tried the following way. It's almost easy for me to handle. Backgroudworker manages this situation perfectly well.
Private Sub BackgroundWorker1_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
Dim i As Integer
For i = 1 To 20000
BackgroundWorker1.ReportProgress((i / 20000) * 100, i)
Threading.Thread.Sleep(1)
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
ProgressBar1.Value = e.ProgressPercentage
ListBox1.Items.Add(e.UserState)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
MsgBox("Complete")
End Sub

Execute a subroutine before exiting the application

I am designing a client-server application, where the client requests various data and the server retrieves it from the local SQL server.
I need to execute a function when the application exits in order to tell associated services that the server is offline, but I am unable to figure out exactly how to do this.
This related question seems to have the answer:
handle the AppDomain.ProcessExit event, which is raised when the application's parent process exits.
however when using the below code the onExit subroutine does not get executed on application closure, probably because I am using the wrong approach to this problem or that Environment.Exit isn't executed when the application exits:
Dim myConn As SqlConnection
Dim cmd As New SqlCommand
Sub Main()
AddHandler AppDomain.CurrentDomain.ProcessExit, AddressOf onExit
End Sub
Public Sub onExit(sender As Object, e As EventArgs)
'Unrelated code removed here.
End Sub
To clarify, I need it to execute onExit when the user closes the application; I am not looking to close the application via a console command.
I've also thought that perhaps I could add a Handle to onExit but I have no idea what to use here.
Is it possible to reliably execute a subroutine or function when the user attempts to close the application via the standard Windows Explorer user interface?
This is a console application, I know this can be very easily done in Windows Forms and thought it would be easy in console. Obviously not.
You need to import the SetConsoleCtrlHandler like in this sample:
Module Module1
Public Declare Function SetConsoleCtrlHandler Lib "kernel32" (Handler As ConsoleCtrlDelegate, Add As Boolean) As Boolean
Public Delegate Sub ConsoleCtrlDelegate()
Sub Main()
SetConsoleCtrlHandler(New ConsoleCtrlDelegate(AddressOf OnExit), True)
Console.WriteLine("Please try to close down...")
Console.ReadLine()
End Sub
Sub OnExit()
MsgBox("Help I'm being closed!")
End Sub
End Module

Optionally launch form in VB.Net console application

So I've set my application to a console type application and pointed it to a module containing just Sub Main, i.e.
Module mdlConsole
Sub Main(ByVal cmdArgs() As String)
If cmdArgs.Length = 0 Then
Dim frm As New frmMain
frm.Show()
End If
End Sub
End Module
Ideally if no arguments are supplied then the program would simply launch the primary form. The goal is to make this program (optionally) script-able from the command line. If arguments are supplied then the application form is not loaded and processes its features based off the command line arguments supplied.
As it is now, the program runs, briefly launches the form (frmMain) and then closes. What am I doing wrong or missing?
If you're not keen on giving me the answer, I'd be happy to be pointed in the right direction also. I don't expect anyone to just supply answers. I need to learn also.
Thanks!
For Winforms, you need to 'run' the App object, passing a form to use:
Sub Main(ByVal cmdArgs() As String)
If cmdArgs.Length = 0 Then
Dim frm As New frmMain
Application.Run(frm)
Else
' cmd line version
End If
End Sub
I see in your comment that you'd like to remove the console window that appears when running the form version of the program with the solution currently proposed. I cannot comment due to lack of reputation, so I will make this a full-fledged answer.
Consider approaching this from an inverse perspective: if you write the program as a forms application, opening it by default will bring up the form. But in the Form1_Load event, check the command line arguments; if they are greater than 0, simply run your (abbreviated) code logic here. At the end of the code, simply run Application.Exit(), like so:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
If My.Application.CommandLineArgs.Count > 0 Then
' Execute (abbreviated) code logic
' When finished, exit the program
Application.Exit()
End If
End Sub
This can also make your code cleaner and more practical if you're relying on a user-interface, because you can still access the values of form elements that the user would otherwise be modifying - but without the form showing on the screen (unless you prompt it to with a MsgBox or such).
This also works very nicely for Scheduled Tasks, as the user can run them manually with a user-interface, while the program executes without being visible via a scheduled task.
Kind of a follow-on to Chad's solution above I used the steps defined in How to have an invisible start up form? to avoid showing my form.
In short, create an Overrides subroutine that gets launched before Form1_Load:
This worked for me:
Protected Overrides Sub SetVisibleCore(ByVal value As Boolean)
If Not Me.IsHandleCreated Then
Me.CreateHandle()
value = False
MyBase.SetVisibleCore(value)
Else
Exit Sub
End If
If My.Application.CommandLineArgs.Count > 0 Then
MsgBox("Argument Sensed!")
' Execute (abbreviated) code logic
' When finished, exit the program
Me.Close()
Application.Exit()
Else
MyBase.SetVisibleCore(True)
End If
End Sub

How can I get a program to run automatically at specific times a day (VB.NET)

The application I'm developing right now allows the user to update an Excel sheet or Sql database for set metrics twice a day. The program does this by popping up at certain times (e.g. 6:00 AM, 5:00 PM, 3:42 PM, whatever the user sets). By having the program pop up at certain times, the program ("Auto Excel It!!!") allows you as the user to track set data (say, sales calls, sales presentations, meetings, number of hours coding, number of jalepeƱo burritos eaten, etc.).
How can a developer get this program to "pop up"/start/function automatically at specific times through the means of the Windows Scheduler API (or something better)?
Here's how my understanding's evolved lately:
Nothing --> Use Timers As The Program Runs In The Background --> Use Windows Scheduler's API To Run Automatically (Current) --> Possible New Understanding From Your Answer
For example, I'm aware of: DispatcherTimers, Timers, another timer I'm not aware of, Sleep(), Windows Scheduler. But with these in mind, I don't know what to do regarding the following: Automatically starting a program via Windows Scheduler; Preserving computer resources if a timer is used; or even how to get this top pop up automatically.
Update 1:
#nfell2009:Your logic helped me out big time. At first I had to toy around with converting your Timer here to a DispatcherTimer (WPF forms standard, it seems). Then, I switched the the "Handles" for the Sub tCheckTime to "AddHandler tCheckTime.Tick, AddressOf tCheckTime_Tick" --- Why I had to do this is a good question.
Then, once I had the basic EventHandlers set up, your idea for comparing the user's text (As Date) to the System.Date is good--When I screwed something up and couldn't get the code to work, I switched it up and converted System.Date to a String--i.e. I went from String->Date To Date->String... That's when I got the Timer to work. When my System.Time ticked to 3:12 PM, the MsgBox popped up with "Your Message Here."
(A Quick (Evil) Thank You! I've spent four-plus hours getting this to work)
Code:
From Using "Handles" At tCheckTime_Tick (Which seems like it 'should' work)
Private Sub tCheckTime_Tick(sender As Object, e As EventArgs) Handles tCheckTime.Tick
...
End Sub
To AddHandler blah, AddressOf tCheckTime_Tick (Does work)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Loaded
'MsgBox(Now().ToString("hh:mm")) 'String.Format("{hh:mm}", Now()))
AddHandler tCheckTime.Tick, AddressOf tCheckTime_Tick 'Why is this necessary?
tCheckTime.Interval = New TimeSpan(0, 1, 0)
End Sub
Public Class Form1
Dim iSetTime As Date
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub btnSetTime_Click(sender As Object, e As EventArgs) Handles btnSetTime.Click
If (tCheckTime.Enabled = True) Then
tCheckTime.Enabled = False
End If
iSetTime = txtTHour.Text + ":" + txtTMinute.Text + ":" + txtTSecond.Text
tCheckTime.Enabled = True
End Sub
Private Sub tCheckTime_Tick(sender As Object, e As EventArgs) Handles tCheckTime.Tick
If (TimeOfDay = iSetTime) Then
MsgBox("Your Message")
End If
End Sub
End Class
You will need error checking for the textboxs, but its simply:
3 textboxs with indication of which is which, so maybe a label each with H, M, S - or something.
A button which will set time and a timer. Naming:
Textboxs
Hours = txtTHour
Minutes = txtTMinute
Seconds = txtTSecond
Buttons
Start Button = btnSetTime
Timers
Timer = tCheckTime
I can think of two easy ways:
Have your program calculate the time until it should next appear in seconds and then set a timer with an elapsed time such that when the tick event is raised you can do whatever you need to do.
Use MS Task Manager to launch your program when and as needed.

When I call an pre-existing event handler from a subroutine, it doesn't come back. VB

I'm looking to call a pre-existing event handler subroutine from the form_Load event handler.
But the below doesn't work because control doesn't come back and I want to do more.
UPDATE:
I'm new to this so I don't know what the proper protocol is but...
The reason for the non-return was that a statement like the below ended the subroutines execution.
If aLabel.Tag = 1...
the fix was adding New to the declaration to create an instance of it, ie..
changing....
Dim aLabel As Label
... to ...
Dim aLabel As New Label
I'm surprised I didn't get a warning but instead they just abruptly stopped execution of the sub. That wasn't very helpful :)
Thanks again for your time guys...
(Maybe this question should be deleted now that it has served its purpose)
#konrad #karl
END OF UPDATE
What doesn't work is....
Private Sub Form1_Load...
button1_Click(sender, e) 'But Control doesn't come back.
end sub
Do I change the sender to something?
Thanks in advance
Dave
Invoking event handlers like this is a bad idea, because you are trying to simulate the event context by making sender and/or EventArgs be something else.
Instead, put the logic that you want to invoke into a Subroutine or Function and have your Form1_Load method call that; likewise if you really do have a real click event handler, then that handler code can call the method too, like this:
Private Sub Form1_Load()
DoSomeWork()
End Sub
Protected Sub button1_Click(ByVal sender As Object, ByVal e As EventArgs)
DoSomeWork()
End Sub
Private Sub DoSomeWork()
' Put logic here that you want to do from form load and a button click
End Sub
This has the benefit of making the code cleaner, clearer and easier to maintain as you only need to change the logic in one place should you need to change the logic.
Note: Obviously, you can pass parameters to the DoSomeWork method, if need be, and change it to a Function if you need it to return something.