VB.Net Updating UI from background thread - vb.net

I’m working on a scheduler like project using VB.Net, the application start from “Sub Main” with Application.Run(), all program codes are handler in a class, which is created and started here,
Public Sub Main()
m_App = New myApp
m_App.Start()
Application.Run()
End Sub
Inside the myApp, there has a timer to control the execution of task, and it will start a thread for each task, when the task complete, we try to display the alert window if there has error detected. We have tested two different way for communization between the execute thread and the main thread in order to display the alert window (frmAlert):
1) by adding pulic event in task object, then addhandler to the function in main thread
2) use delegate to notify the main thread
However, the alert window cannot be shown and there has no error reported. After debuging with the IDE, it was found that the alert windows has successful displayed, but it will closed when the task thread is completed.
Here is a simplified task class (testing with two communization methods),
Public Class myProcess
Public Event NotifyEvent()
Public Delegate Sub NotifyDelegate()
Private m_NotifyDelegate As NotifyDelegate
Public Sub SetNotify(ByVal NotifyDelegate As NotifyDelegate)
m_NotifyDelegate = NotifyDelegate
End Sub
Public Sub Execute()
System.Threading.Thread.Sleep(2000)
RaiseEvent NotifyEvent()
If m_NotifyDelegate IsNot Nothing Then m_NotifyDelegate()
End Sub
End Class
And the main application class
Imports System.Threading
Public Class myApp
Private WithEvents _Timer As New Windows.Forms.Timer
Private m_Process As New myProcess
Public Sub Start()
AddHandler m_Process.NotifyEvent, AddressOf Me.NotifyEvent
m_Process.SetNotify(AddressOf NotifyDelegate)
ProcessTasks()
End Sub
Private Sub Timer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles _Timer.Tick
ProcessTasks()
End Sub
Public Sub ProcessTasks()
_Timer.Enabled = False
'
Dim m_Thread = New Thread(AddressOf m_Process.Execute)
m_Thread.Start()
'
_Timer.Interval = 30000
_Timer.Enabled = True
End Sub
Public Sub NotifyEvent()
frmAlert.Show()
End Sub
Public Sub NotifyDelegate()
frmAlert.Show()
End Sub
End Class
It was found that the frmAlert is shown by using either NotifyEvent or NotifyDelegate, but it is closed immediately when the Execute is finished.
May I know how we can popup an alert window from the execution thread which can stay in the screen until user close it?
Thanks in advance!

You need to ensure that your main thread does not terminate if you want it to do anything when a sub-thread raises and event.
Public Sub Start()
AddHandler m_Process.NotifyEvent, AddressOf Me.NotifyEvent
m_Process.SetNotify(AddressOf NotifyDelegate)
ProcessTasks()
Do While True 'You might want to add a boolean condition here to instruct the main program to terminate when you want it to.
System.Threading.Thread.Sleep(200)
Loop
End Sub
This will prevent the main thread (program) to end, and thus will be available to handle any events raised by the sub-threads. Note: Your class lacks a termination condition.

Related

Threading and modal form windows

VB.Net code.
I have a program where I am running a process in a thread and in that thread I need to have a pop up message information box that is non-modal. The main process is in a thread because it has to run in parallel and the user can initiate this process many times at the same time.
I read that the modal message box needs to be a custom form that is also ran from a thread to not block the program from continuing on. such as .Show() stops the program and waits for the user input. And you have to use .ShowDialog() via a thread
My code:
Calling initial thread:
Public Event Report As EventHandler
'In a method
Task.Run(Function() BackgroundThread())
Private Function BackgroundThread() As Task()
RaiseEvent Report(Me, New System.EventArgs)
End Function
In the Report method I have a snippet of code that then calls the form window to pop up the modal window:
Private mDiaplayMessageBox As NonModalPopUp
Private Sub DisplayMessageBox()
mDiaplayMessageBox = New NonModalPopUp()
Task.Run(Sub() mDiaplayMessageBox.ShowDialog())
End Sub
The issue I am having is that when I am finished with the report method I want to close this popup message. But when there is more than one of these pop up windows open at a time, only the last window opened will close and the program loses the handle I think to the other pop up windows and they will not close.
To close the windows I have in the modal form this code
Public Sub CloseMe()
'This will grab the thread that this window is running on, solves Cross-Threading issue.
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(AddressOf CloseMe))
Exit Sub
End If
Me.BackColor = Color.Red
Me.Close()
End Sub
This first time this code is called its will hit the Me.Invoke and then close the window. However, on any subsequent calls when it gets to Me.InvokeRequired this will then be set to false, not called the Me.Invoke and go to the Me.Close() but it will not close the window.
I tried to do something where I grab the Handle intptr value but when ever I vent just look at that value the program immediately throws a cross-threading exception.
All I want to do is close the other windows which does not seem like a hard task but I do not know what I am missing.
One of approaches you can follow to achieve your goal might be as code below shows:
You can create a custom event which you can use as a “call” to listen to for the closure of your form.
Public Class Form1
Dim frm2 As Form2
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
frm2 = New Form2
Task.Run(Sub()
AddHandler CloseFrm2, Sub()
Dim CloseMe As Action = Sub()
frm2.Close()
frm2.Dispose()
End Sub
If frm2.InvokeRequired Then
frm2.Invoke(Sub() CloseMe())
Else
CloseMe()
End If
End Sub
frm2.ShowDialog()
End Sub)
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
RaiseEventCloseFrm2()
End Sub
End Class
Module EventHelper
Public Event CloseFrm2()
Sub RaiseEventCloseFrm2()
RaiseEvent CloseFrm2()
End Sub
End Module

WinForms.IllegalCrossThreadCall with filewatcher

I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.

Executes other form periodically

In Vb.net, I want to show my main form and other form periodically.
The last form does check operations and automatically closes.
Should be implementing threads, but I am not sure if it's possible...
My code is empty..
Imports System
Imports System.Windows.Forms
Friend NotInheritable Class Program
Private Sub New()
End Sub
<STAThread() _
Shared Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(false)
Application.Run (New formMain()) '--> Main Form
'All code here is not execute until Main Form is closed
End Sub
End Class
Thanks in advance.
Edit: Sorry, I am a newbie in vb.net, I would want to say show my main form.
Edit 2: I think I didn't explain well, but with the help of #JeremyThompson I can complete my code.
FormMain: Now, When I try to close this form, I show another form to do check operations but I hold this (FormMain) always visible.
Private Sub FormMain_FormClosing(sender as Object, e as FormClosingEventArgs) _
Handles FormMain.FormClosing
Me.Visible = True
Dim anotherForm as New CheckOperationsForm()
anotherForm.Show()
e.Cancel = True
End Sub
Other form (or FormCheckOperations()): When the routine coded in this form is completed I establish the value of a boolean _successfulChecking, so if this boolean is true, main form keeps on showing, in another case, the application ends.
Private Sub FormCheckOperations_FormClosing(sender as Object, e as FormClosingEventArgs) _
Handles FormCheckOperations.FormClosing
If Not _succesfulChecking Then
End 'Close application
End If
End Sub
My doubt is, how I can show FormCheckOperation periodically from MainForm (I could call FormMain.Close() to do it) or how do it from Main Sub?
Edit 3: Now, In my current approach, I open 2 threads in Main Sub, the first one with the Main Form and the second one with another thread which opens CheckOperations Forms each 60 seconds. But when executes in Visual Studio, forms stay "behind" of the SDK, furthermore, they are not working properly, but I think the final way should be closer.
Imports System
Imports System.Windows.Forms
Friend NotInheritable Class Program
Private Sub New()
End Sub
<STAThread() _
Shared Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(false)
Dim threadFormMain As New Thread (AddressOf launchFrmMain)
Dim threadFormCheckOperations As New Thread (AddressOf launchThreadFrmCheckOperations)
threadFormMain.Start()
threadFormCheckOperations.Start()
End Sub
Public Shared Sub launchFrmMain()
Application.Run(New FormMain()
End Sub
Public Shared Sub launchThreadFrmCheckOperations()
While(True)
Dim threadForm As New Thread(AddressOf launchFrmCheckOperations)
threadForm.Start()
'Here I should stop while loop during 60 secs…
'Thread.Sleep(60000)
End While
End Sub
Public Shared Sub launchFrmCheckOperations()
Application.Run(New FormCheckOperations()
End Sub
End Class
In the FormMain_Closing event, set Visible = False, instantiate and show another form and set e.cancel = True, then put code in the anotherform's closing event to End the application.
eg:
Private Sub FormMain_FormClosing(sender as Object, e as FormClosingEventArgs) _
Handles FormMain.FormClosing
Me.Visible = False
Dim anotherForm as New AnotherForm()
anotherForm.Show()
e.Cancel = True
End Sub
Finally I solved this.
In Main Sub firstly launches a thread with check operation forms (globally declared) and later launches mainForm in main thread.
The thread with check operations contains while loop which creates a new thread in each iteration, but with Thread.Join() stays waiting the end of previous thread, so it avoids to create other thread while there is a thread opened.
The code is the following:
Imports System
Imports System.Windows.Forms
Friend NotInheritable Class Program
Private Sub New()
End Sub
<STAThread() _
Shared Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(false)
threadFormCheckOperations.Start()
Application.Run(New FormMain())
End Sub
Public Shared threadFormCheckOperations As New Thread(AddressOf launchThreadFrmCheckOperations)
Public Shared Sub launchThreadFrmCheckOperations()
While(True)
Dim threadForm As New Thread(AddressOf launchFrmCheckOperations)
threadForm.Start()
threadForm.Join() '---> Wait until thread is closed
Thread.Sleep(60000)
End While
End Sub
Public Shared Sub launchFrmCheckOperations()
Application.Run(New FormCheckOperations()
End Sub
End Class

How can the BackgroundWorker get values from the UI thread while it is running?

I am using the BackgroundWorker to do the heavy tasks so the UI thread doesn't get blocked. While the BackgroundWorker can send values to the UI thread using the progress-scheme, how can the BackgroundWorker get some values FROM the UI thread?
Either by asking it or simply by the UI thread sending some values to the BackgroundWorker?
Just accessing a variable of the UI thread like UIForm.x within the BackgroundWorker does not work, it does not seem to have access to the UI variables???
Many thanks
Other threads than the UI thread are not allowed to access the UI. You probably started the BackgroundWorker with worker.RunWorkerAsync(). You can also start it with worker.RunWorkerAsync(someObject). While the worker is running, you cannot pass new objects, but you can alter the contents of the object itself. Since object types are reference types, the UI thread and the worker thread will see the same object content.
Imports System.ComponentModel
Imports System.Threading
Class BgWorkerCommunication
Private _worker As BackgroundWorker
Private Class WorkParameters
Public text As String
End Class
Public Sub DoRun()
Dim param = New WorkParameters()
_worker = New BackgroundWorker()
AddHandler _worker.DoWork, New DoWorkEventHandler(AddressOf _worker_DoWork)
AddHandler _worker.RunWorkerCompleted, New RunWorkerCompletedEventHandler(AddressOf _worker_RunWorkerCompleted)
param.text = "running "
_worker.RunWorkerAsync(param)
While _worker.IsBusy
Thread.Sleep(2100)
param.text += "."
End While
End Sub
Private Sub _worker_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Console.WriteLine("Completed")
End Sub
Private Sub _worker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim param = DirectCast(e.Argument, WorkParameters)
For i As Integer = 0 To 9
Console.WriteLine(param.text)
Thread.Sleep(1000)
Next
End Sub
End Class

vb.net - background thread issue

For some reason a background thread in my app can't change any labels, textbox values, etc on my main form. There is no compile errors, when the thread executes nothing happens.
Here is some example code:
Imports System.Threading
Public Class Class1
Dim tmpThread As System.Threading.Thread
Private Sub bgFindThread()
Form1.lblStatus.Text = "test"
End Sub
Public Sub ThreadAction(ByVal Action As String)
If Action = "Start" Then
tmpThread = New System.Threading.Thread(New System.Threading.ThreadStart(AddressOf bgFindThread))
tmpThread.Start()
ElseIf Action = "Abort" Then
If tmpThread.IsAlive = True Then tmpThread.Abort()
End If
End Sub
End Class
Can someone let me know what I'm doing wrong?
AFAIK code above will throw an exception IllegalCrossThreadException, it is because the background thread is not the same as UI thread and background try to set value on other thread. So windows form check every thread that work properly.
You can set Control.CheckForIllegalCrossThreadCalls to false to make it works.
Code below is when setting property is not run
Add into your code
------------------------------
Delegate Sub MyDelegate()
Private Sub RunMyControl()
lblStatus.Text = "test"
End Sub
Change your code
------------------------------
Private Sub bgFindThread
lblStatus.BeginInvoke (New MyDelegate(AddressOf RunMyControl))
End Sub
The method asyncronsly run code from background thread to UI thread.
You can only access UI controls from the UI thread.
I suggest reading this first: http://www.albahari.com/threading/
As others have mentioned, it is forbidden (for good reasons) to update UI elements from a non-UI thread.
The canonical solution is as follows:
Test whether you are outside the UI thread
If so, request for an operation to be performed inside the UI thread
[Inside the UI thread] Update the control.
In your case:
Private Sub bgFindThread()
If lblStatus.InvokeRequired Then
lblStatus.Invoke(New Action(AddressOf bgFindThread))
Return
End If
lblStatus.Text = "test"
End Sub
The only thing that changed is the guard clause at the beginning of the method which test whether we’re inside the UI thread and, if not, requests an execution in the UI thread and returns.
You can use a delegate to update UI controls in a background thread.
Example
Private Delegate Sub bkgChangeControl(ByVal bSucceed As Boolean)
Private dlgChangeControl As bkgChangeControl = AddressOf ChangeControl
Private Sub threadWorker_ChangeControl(ByVal bSucceed As Boolean)
Me.Invoke(dlgChangeControl, New Object() {bSucceed})
End Sub
Private Sub ChangeControl()
Me.lable="Changed"
End Sub
'In your background thread, call threadWorker_ChangeControl.