Vb.net button still allows click while disabled - vb.net

My button is responding to clicks while disabled.
Private Sub btnGenerate_Click(sender As Object, e As EventArgs) Handles btnGenerate.Click
btnGenerate.Enabled = False
Me.Cursor = Cursors.WaitCursor
'Do a bunch of operations
Me.Cursor = Cursors.Default
btnGenerate.Enabled = True
End Sub
It takes about 5-10 seconds to process the stuff I'm doing in the background. During that 5-10 seconds the button is greyed out, but if I click it a second time, then it performs the operational stuff a second time after finishing the first.
I'm missing something here. How can I prevent button from allowing interaction until operations are finished?

Dim Working as boolean=false
Private Sub btnGenerate_Click(sender As Object, e As EventArgs) Handles btnGenerate.Click
if Working=true then exit sub
' Your Work Process
Work()
End Sub
sub Work()
Working=True
' Work code
Working=False
end sub
this should prevent the double click

With VS2012, Async Work is very easy to use (compared to previous versions...).
The problem is the UI thread is not letting go. Without seeing what 'work' is actually going on, I can not explain why. I am hoping nothing that re enables the button...
However, Async will allow release of the UI thread and the enabled = false should take effect. Try something like this:
Private Async Sub btnGenerate_Click(sender As Object, e As EventArgs) Handles btnGenerate.Click
btnGenerate.Enabled = False
Dim t As New Task(Sub() MyWorkLoad())
t.Start()
Await t
btnGenerate.Enabled = True
End Sub
Private Sub MyWorkLoad()
'do your work here
'for testing
Dim time As Date = Now
Do While True
If DateAdd(DateInterval.Second, -5, Now) > time Then Exit Do
Loop
End Sub
This did work for me...

Related

Cleanly stopping a thread in VB.net to avoid double error handling

I’ve got this issue with stopping a thread cleanly. I’ve tried to simplify it into a more basic version of the code below and I’m wondering if my approach is completely wrong here.
I have Form1 with a bunch of UI elements which need updating as BackgroundCode runs (I run it here so it’s a separate thread and it doesn’t hold up the UI) I then update the UI by invoking a sub
(Me.Invoke(Sub()
something.property=something
End Sub))
I’m also trying to handle some errors handed to the application by an external file. I’ve used a timer to check for the file and if it exists I grab the contents and pass it to my ErrorHandler. This Writes the Error out to a log file, displays it on screen and then aborts the background worker so that the program doesn’t continue to run. The trouble I’m getting is that by executing BackgroundThread.Abort() that action itself is triggering the ErrorHandler. Is there a way to ask the BackgroundThread to stop cleanly? I want BackgroundThread to trigger the ErrorHandler if something else goes wrong in that code.
I’m wondering about using a global boolean like “ErrorIsRunning” to restrict the ErrorHandler sub so that it can only ever run once, but this is starting to feel more and more hacky and I’m wondering if I’ve gone completely off track here and if there might be a better way to approach the entire thing.
Public Class Form1
Dim BackgroundThread As New Thread(AddressOf BackgroundCode)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘Hide Error Page
ErrorPage.Visible = False
ErrorLabel.Visible = False
‘Start Background Code
BackgroundThread.Start()
End Sub
Private Sub BackgroundCode()
Try
‘<Background code which runs over a number of minutes>
Catch.ex as Exception
ErrorHandler(“Error with BackgroundCode: “ + ex.Message)
End Try
End Sub
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Dim ErrorFile As String = “C:\MyErrorFile.Err”
Dim ErrorContents As String
If File.Exists(ErrorFile) Then
Timer.Enabled = False
ErrorContents = File.ReadAllText(ErrorFile).Trim()
ErrorHandler(ErrorContents)
End If
End Sub
Public Sub ErrorHandler(ErrorText As String)
WriteLog(“ERROR” + ErrorText)
Me.Invoke(Sub()
Me.ErrorPage.Visible = True
Me.ErrorLabel.Text = ErrorText
End Sub)
BackgroundThread.Abort()
End Sub
End Class
Never abort threads.
This uses a Task and a ManualResetEvent. Without seeing the code inside of the background task it is hard to know how many stop checks might be needed.
Public Class Form1
Private BackgroundTask As Task
Private BackgroundTaskRunning As New Threading.ManualResetEvent(True)
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Hide Error Page
ErrorPage.Visible = False
ErrorLabel.Visible = False
'Start Background Code
BackgroundTask = Task.Run(Sub() BackgroundCode())
End Sub
Private Sub BackgroundCode()
Try
'<Background code which runs over a number of minutes>
'put stop checks periodically
' e.g.
If Not BackgroundTaskRunning.WaitOne(0) Then Exit Sub 'stop check
Catch ex As Exception
ErrorHandler("Error with BackgroundCode: " + ex.Message)
End Try
End Sub
Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick
Dim ErrorFile As String = "C:\MyErrorFile.Err"
Dim ErrorContents As String
If File.Exists(ErrorFile) Then
Timer.Enabled = False
ErrorContents = File.ReadAllText(ErrorFile).Trim()
ErrorHandler(ErrorContents)
End If
End Sub
Public Sub ErrorHandler(ErrorText As String)
WriteLog("ERROR" + ErrorText)
Me.Invoke(Sub()
Me.ErrorPage.Visible = True
Me.ErrorLabel.Text = ErrorText
End Sub)
BackgroundTaskRunning.Reset() 'stop task <<<<<<<<<<<<<<<<<<<<<<<<<<<
End Sub
End Class

How to do a backgroundworker about SAPI SPEAK

I want to create a SAPI speaking background worker in Visual Basic .NET in order to allow my client to continue doing something while listens to the SAPI talk.
I've reached that point, but the problem is if I want to reproduce another speaking, I cannot cancel the current speaking and occurs an exception.
I have the following code:
'MODULE IMPORTED IN THE MAIN WORK: argsBackgroundWorker.vb
Public Class argsBackgroundWorker
Public text_to_speak As String
End Class
Private talk As argsBackgroundWorker = New argsBackgroundWorker()
Private Sub sapitalk_background_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles sapitalk_background.DoWork
If (My.Settings.help_voice = True) Then 'PASS TRUE
Dim reproduceText
Dim text As argsBackgroundWorker = e.Argument
'I have put this code to cancel... So? :(
If sapotalk_background.CancellationPending Then Exit Sub
reproduceText = CreateObject("Sapi.spvoice")
reproduceText.speak(talk.text_to_talk)
Else
sapitalk_background.CancelAsync()
End If
End Sub
Private Sub btn_saysomething_Click(sender As Object, e As EventArgs) Handles btn_saysomething.Click
'Support in order to cancel tasks.
sapitalk_background.WorkerSupportsCancellation = True
talk.text_to_speak = "SOMETHING SOOOOOO SOO SOOOOOO LONG..."
'Cancel another text being spoken.
sapitalk_background.CancelAsync()
'Then, talk the new text.
sapitalk_background.RunWorkerAsync(talk)
End Sub
Private Sub principal_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Support in order to cancel tasks.
sapitalk_background.WorkerSupportsCancellation = True
talk.text_to_speak = "SOMETHING SOOOOOO SOO SOOOOOO LONG..."
'Cancel another text being spoken.
sapitalk_background.CancelAsync()
'Then, talk the new text.
sapitalk_background.RunWorkerAsync(talk)
End Sub
It works great as background speaking but notice that when I compile the app, it starts speaking a long text. And if I click a button to cancel the current speaking and speak another text, it fails and shows me that it is running a current background worker.
The only idea I have is
While Not sapitalk_background.IsBusy
sapitalk_background.RunWorkerAsync(talk)
End While
If it gets stuck in this loop until the voice stops then I am thinking that you cant stop the voice once its running on a bg thread.

Constantly monitor if a process is running

I have the following code:
Dim p() As Process
Private Sub CheckIfRunning()
p = Process.GetProcessesByName("skype") 'Process name without the .exe
If p.Count > 0 Then
' Process is running
MessageBox.Show("Yes, Skype is running")
Else
' Process is not running
MessageBox.Show("No, Skype isn't running")
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
CheckIfRunning()
End Sub
And it works GREAT!
But I'm wondering how I would convert this to a monitoring application, to constantly check if the processes is running. Is it as simple as putting the check on a timer every 1 second, or is there a better, more efficient way to go about this.
In the end result, I'd like to have a label that says "Running", or "Not Running" based on the process, but I need something to watch the process constantly.
If you need the app running all the time, then you don't need a Timer at all. Subscribe to the Process.Exited() event to be notified when it closes. For instance, with Notepad:
Public Class Form1
Private P As Process
Private FileName As String = "C:\Windows\Notepad.exe"
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ps() As Process = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(FileName))
If ps.Length = 0 Then
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
Else
P = ps(0)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End If
End Sub
Private Sub P_Exited(sender As Object, e As EventArgs)
Console.WriteLine("App Exited # " & DateTime.Now)
Console.WriteLine("Restarting app: " & FileName)
P = Process.Start(FileName)
P.EnableRaisingEvents = True
AddHandler P.Exited, AddressOf P_Exited
End Sub
End Class
That would keep it open all the time, assuming you wanted to open it if it wasn't already running.
If you don't want to open it yourself, and need to detect when it does open, then you could use WMI via the ManagementEventWatcher as in this previous SO question.
I've done something similar to this to monitor an exe that I need to be running all the time, and to restart it if it was down.
Mine was running as a Windows Service - that way it would start when windows booted and id never need to look after it.
Alternatively you could just create it as a console app and put it in your startup folder?
I had:
Sub Main()
Do
Check_server()
Dim t As New TimeSpan(0, 15, 0)
Threading.Thread.Sleep(t)
Loop
End Sub
Public Sub Check_server()
Dim current_pros() As Process = get_pros()
Dim found As Boolean = False
If Now.Hour < "22" Then
For Each pro In current_pros
If pro.ProcessName.ToLower = "Lorraine" Then
found = True
Exit For
Else
found = False
End If
Next
If found Then
Console.WriteLine("Server up")
Else
Console.WriteLine("Server down - restarting")
restart_server()
End If
End If
End Sub
My "server" app was called Lorraine...Also a timer maybe better practice than having the thread sleep..
From my experience, a simple timer works best:
'Timer interval set to 1-5 seconds... no remotely significant CPU hit
Private Sub timerTest_Tick(sender As System.Object, e As System.EventArgs) Handles timerTest.Tick
Dim p() As Process = Process.GetProcessesByName("Skype")
lblStatus.Text = If(p.Length > 0, "Skype is running.", "Skype isn't running.")
End Sub
Your mileage may vary, but I don't like to deal with separate threads unless necessary.

How to properly cancel a background worker that works forever?

Folks, I have a vb.net application in which I start a background worker when a button is pushed. The BGW works forever in a Do loop unless I push another button in which case it should be stopped, some other work performed and then the BGW started anew.
I have two versions of the code to date, based on research online, neither of which work though give differing results. Code so far is:
Dim autoEvent As New AutoResetEvent(False)
Private Sub StartWorkerButton_Click () Handles StartWorkerButton.Click
MyWorker.WorkerSupportsCancellation = True
MyWorker.WorkerReportsProgress = True
MyWorker.RunWorker.Asunc()
End Sub
Private Sub MyWorker_ProgressChanged () Handles MyWorker.Progresschanged
'Update some text boxes text string here based on data from the BGW work
End Sub
Private Sub StopThenRestartButton_Click Handles StopThenRestartButton.Click
If MyWorker.IsBusy Then
MyWorker.CancelAsync()
autoEvent.WaitOne()
' Do some work here
MyWorker.RunWorkerAsync() ' Restart BGW - But this fails!
Else
' Do same work here but without stopping and restarting BGW
End If
Exit Sub
Private Sub MyWorker_DoWork () Handles MyWorker.DoWork
Do While 1
If MyWorker.CancellationPending = True
e.Cancel = True
autoEvent.set
Exit Sub
Else
' Do some task work over and over again
'ReportProgress() here as well
End If
End Sub
Whichever way I work it it seems like execution can request the cancellation, set the event, but then will not restart the BGW because it seems to still be running. Gr.
You should allow event processing on the main thread and also wait till the Worker finishes. Actually you don't need the autoEvent
Private Sub StopThenRestartButton_Click() Handles StopThenRestartButton.Click
If MyWorker.IsBusy Then
MyWorker.CancelAsync()
autoEvent.WaitOne()
While MyWorker.IsBusy
'Process the events
Application.DoEvents()
End While
' Do some work here
MyWorker.RunWorkerAsync() ' Restart BGW
Else
' Do same work here but without stopping and restarting BGW
End If
End Sub
Here is the code without autoEvent
Private Sub StartWorkerButton_Click() Handles StartWorkerButton.Click
MyWorker.WorkerSupportsCancellation = True
MyWorker.WorkerReportsProgress = True
MyWorker.RunWorkerAsync()
End Sub
Private Sub MyWorker_ProgressChanged() Handles MyWorker.ProgressChanged
'Update some text boxes text string here based on data from the BGW work
End Sub
Private Sub MyWorker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles MyWorker.RunWorkerCompleted
Console.WriteLine("RunWorkerCompleted Called")
End Sub
Private Sub StopThenRestartButton_Click() Handles StopThenRestartButton.Click
If MyWorker.IsBusy Then
MyWorker.CancelAsync()
While MyWorker.IsBusy
Application.DoEvents()
End While
Console.WriteLine("Worker Finished. Going to restart")
' Do some work here
MyWorker.RunWorkerAsync() ' Restart BGW
Else
' Do same work here but without stopping and restarting BGW
End If
End Sub
Private Sub MyWorker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles MyWorker.DoWork
Do While 1
If MyWorker.CancellationPending = True Then
e.Cancel = True
Exit Do ' Exit Sub also works here, if you have nothing to do after the loop
Else
' Do some task work over and over again
'ReportProgress() here as well
End If
Loop
End Sub

Threading System.NullReferenceException

I am trying to start and stop a autochecking function when checking or unchecking a checkbox.
Private Sub CheckBoxautorefresh_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBoxautorefresh.CheckedChanged
Dim AutoRefreshThread As Thread = Nothing
If CheckBoxautorefresh.Checked Then
AutoRefreshThread = New Threading.Thread(AddressOf Main.AutoRefresh)
AutoRefreshThread.SetApartmentState(Threading.ApartmentState.STA)
AutoRefreshThread.Start()
Else
AutoRefreshThread.Abort()
End If
End Sub
When I check the Checkbox it starts AutoRefresh-Sub fine, and it works. When I unselect it after that, I get a System.NullReferenceException in this line:
AutoRefreshThread.Abort()
The Autorefresh function just downloads a string every 30 seconds.
And I like to check this autorefresh on/off with a checkbox.
But for some reason it doesn't work.
Can someone help me out? :)
You're defining the thread inside the CheckedChanged event:
Dim AutoRefreshThread As Thread = Nothing
When the checkbox is unchecked, you're referencing a variable that has not actually been instantiated (that only happens when the checkbox is checked). You're no longer referencing the original thread you created when the checkbox was checked.
Try defining AutoRefreshThread outside of the event.
I am not a big fan of .Abort, a lot of gotchas. See http://msdn.microsoft.com/en-us/library/5b50fdsz%28v=vs.110%29.aspx
Try this pattern.
Dim thrd As Threading.Thread
Dim thrdStopped As New Threading.ManualResetEvent(False)
Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
If CheckBox1.Checked Then
If IsNothing(thrd) OrElse thrd.ThreadState <> Threading.ThreadState.Background Then
thrdStopped.Reset() '=false
thrd = New Threading.Thread(AddressOf someThread)
thrd.IsBackground = True
thrd.Start()
End If
ElseIf Not IsNothing(thrd) AndAlso thrd.ThreadState = Threading.ThreadState.Background Then
thrdStopped.Set() '=true
thrd.Join()
End If
End Sub
Private Sub someThread()
Do
Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.ff"))
Loop While Not thrdStopped.WaitOne(100) 'loop while false, sleep 100 ms.
End Sub