How to manupilate parameters/arguments parsed through Raised Events? - vb.net

I have this text loader class that I'm trying to write tests for. And one of it's methods does a RaiseEvent with a CancelEventArgs parsed in as an argument, so something like this:
Private Sub FireThisEvent()
cancelEvent created here
RaiseEvent HelloWorld(cancelEvent)
If cancelEvent.Cancel Then
'do smthg
End If
End Sub
The handler for HelloWorld event is my UI class that makes a pop-up for the user to
decide Yes or No, which then sets cancelEvent.Cancel to either True or False. And then the above method checks on cancelEvent and does an action accordingly.
My question is, since I'm only testing the loader class (and not the UI), how do I manipulate the cancelEvent after the event is raised so that I can test for when cancelEvent.Cancel is True and then , when it is False. Thank you.
Would I have mock the UI class?

My solution to this was to add an event handler in the test method, so that when the event is raised, the test method will create a CancelEventAgrs and set its Cancel to True/False.
Public Sub TestingMethod()
Dim txt As TextLoader = Nothing
AddHandler TextLoader.LoadingDoneEvent,
(Sub(e As ComponentModel.CancelEventArgs)
e.Cancel = True
End Sub)
txt = New TextLoader()
txt.FireThisEvent()
End Sub

Related

Updating DataGridView.BackColor on a background thread

I have an application with a DataGridView on which multiple people could be working at the same time. I want to have each user's current row location displayed via a different colour row in the DataGridView.
Previously I was doing all of this updating via the RowEnter event however the performance is not satisfactory, for obvious reasons.
I'm trying to have a background thread which loops every 10 seconds to populate a DataTable with keys of the other users' locations which then references a key column in the DGV, and if they match, change the DGV row background color else set it to the default.
My current code, below, loops every 10s but it doesn't actually update the DGV.
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ActiveThread = True
dgvThread = New Thread(AddressOf UpdateDGVFromThread) With {
.IsBackground = True}
dgvThread.Start()
End Sub
Public Sub UpdateDGVFromThread()
Do While ActiveThread = True
'Sets table with key values
dtUsers = CLS_USERS.GetUsers(User)
'Loop through them
For Each row As DataRow In dtUsers.Rows
intSeq = row("SEQUENCE")
'Loop through each DGV row and compare the values
For Each dgv_row As DataGridViewRow In dgvCandList.Rows
dgvCandList.BeginInvoke(
Sub()
If dgv_row.Cells("CURRENT_CAND_SQ").Value = intSeq Then
dgv_row.DefaultCellStyle.BackColor = Color.DarkCyan
Else
dgv_row.DefaultCellStyle.BackColor = Color.Cyan
End If
End Sub)
Next
Next
Thread.Sleep(10000)
Loop
End Sub
I tried using dgv.Invoke() rather than .BeginInvoke() but this seemed to lock up the UI thread constantly and only the DGV was unlocked.
Can anyone point me in the right direction?
The BeginInvoke method is used to asynchronously invoke a method delegate on the thread that created the Control's handle. The UI thread, here. It's signature is:
Public Function BeginInvoke (method As Delegate) As IAsyncResult
The method Delegate is then declared in the same thread where the Control invoked has been created.
The delegate should then be declared like this:
In the UI thread:
Delegate Sub MyUpdateDelegate()
Public Sub MyUpdateMethod()
[SomeControl].Text = "Updated Text"
End Sub
In another thread:
Private Sub InvokeFromAnotherThread()
'Prefer the Parent Form as marshaller
Me.BeginInvoke(New MyUpdateDelegate(AddressOf MyUpdateMethod))
'(...)
'You can also use a Control, but the Parent Form is better
[SomeControl].BeginInvoke(New MyUpdateDelegate(AddressOf MyUpdateMethod))
End Sub
Using an anonymous method in-place won't cut it.
There's a shortcut, provided by the MethodInvoker delegate:
MethodInvoker provides a simple delegate that is used to invoke a
method with a void parameter list. This delegate can be used when
making calls to a control's Invoke method, or when you need a simple
delegate but do not want to define one yourself.
Using a MethodInvoker delegate, there's no need to declare a delegate in the UI thread. An anonymous method can be used here, it will be invoked in the UI thread:
Private Sub InvokeFromAnotherThread()
'(...)
BeginInvoke(New MethodInvoker(Sub() [SomeControl].Text = "Updated Text"))
'(...)
End Sub
Or:
Private Sub InvokeFromAnotherThread()
'(...)
BeginInvoke(New MethodInvoker(
Sub()
[SomeControl].Text = "Updated Text"
[SomeOtherControl].BackColor = Color.Red
End Sub))
'(...)
End Sub
Why I suggested a Timer:
The thread you're using has one task only: update a Control in the UI thread and then sleep.
To perform this task, it needs to invoke a method in the UI thread. If the reason why the thread has been created is to avoid blocking the UI thread, a Timer will do the same thing. A System.Windows.Forms.Timer, specifically, will raise its Tick event in the UI thread, without cross-thread calls.
The practical effect is more or less the same.

How to make a loader in a separate thread?

I have a main form wich is expected to perfom some long operations. In parallel, I'm trying to display the percentage of the executed actions.
So I created a second form like this:
Private Delegate Sub DoubleFunction(ByVal D as Double)
Private Delegate Sub EmptyFunction()
Public Class LoaderClass
Inherits Form
'Some properties here
Public Sub DisplayPercentage(Value as Double)
If Me.InvokeRequired then
dim TempFunction as New DoubleFunction(addressof DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End if
End sub
Public Sub CloseForm()
If Me.InvokeRequired Then
Dim CloseFunction As New EmptyFunction(AddressOf CloseForm)
Me.Invoke(CloseFunction)
Else
Me.Close()
End If
FormClosed = True
End Sub
End class
My main sub, the one which is expected to perform the long operations is in another form as follows:
Private Sub InitApplication
Dim Loader as new LoaderClass
Dim LoaderThread as new thread(Sub()
Loader.ShowDialog()
End sub)
LoaderThread.start()
Loader.DisplayPercentage(1/10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2/10)
ConnectToDataBase()
Loader.DisplayPercentage(3/10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4/10)
LoadClients()
...
Loader.CloseForm()
End sub
The code works almost 95% of the time but sometimes I'm getting a thread exception somewhere in the sub DisplayPercentage. I change absolutely nothing, I just hit the start button again and the debugger continues the execution without any problem.
The exception I get is: Cross-thread operation not valid: Control 'LoaderClass' accessed from a thread other than the thread it was created on event though I'm using : if InvokeRequired
Does anyone know what is wrong with that code please ?
Thank you.
This is a standard threading bug, called a "race condition". The fundamental problem with your code is that the InvokeRequired property can only be accurate after the native window for the dialog is created. The problem is that you don't wait for that. The thread you started needs time to create the dialog. It blows up when InvokeRequired still returns false but a fraction of a second later the window is created and Invoke() now objects loudly against being called on a worker thread.
This requires interlocking, you must use an AutoResetEvent. Call its Set() method in the Load event handler for the dialog. Call its WaitOne() method in InitApplication().
This is not the only problem with this code. Your dialog also doesn't have a Z-order relationship with the rest of the windows in your app. Non-zero odds that it will show behind another window.
And an especially nasty kind of problem caused by the SystemEvents class. Which needs to fire events on the UI thread. It doesn't know what thread is the UI thread, it guesses that the first one that subscribes an event is that UI thread. That turns out very poorly if that's your dialog when it uses, say, a ProgressBar. Which uses SystemEvents to know when to repaint itself. Your program will crash and burn long after the dialog is closed when one of the SystemEvents now is raised on the wrong thread.
Scared you enough? Don't do it. Only display UI on the UI thread, only execute slow non-UI code on worker threads.
Thank you for your proposal. How to do that please ? Where should I
add Invoke ?
Assuming you've opted to leave the "loading" code of the main form in the main UI thread (probably called from the Load() event), AND you've set LoaderClass() as the "Splash screen" in Project --> Properties...
Here is what LoaderClass() would look like:
Public Class LoaderClass
Private Delegate Sub DoubleFunction(ByVal D As Double)
Public Sub DisplayPercentage(Value As Double)
If Me.InvokeRequired Then
Dim TempFunction As New DoubleFunction(AddressOf DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End If
End Sub
End Class
*This is the same as what you had but I moved the delegate into the class.
*Note that you do NOT need the CloseForm() method as the framework will automatically close your splash screen once the main form is completely loaded.
Now, over in the main form, you can grab the displayed instance of the splash screen with My.Application.SplashScreen and cast it back to LoaderClass(). Then simply call your DisplayPercentage() method at the appropriate times with appropriate values:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitApplication()
End Sub
Private Sub InitApplication()
Dim Loader As LoaderClass = DirectCast(My.Application.SplashScreen, LoaderClass)
Loader.DisplayPercentage(1 / 10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2 / 10)
ConnectToDataBase()
Loader.DisplayPercentage(3 / 10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4 / 10)
LoadClients()
' Loader.CloseForm() <-- This is no longer needed..."Loader" will be closed automatically!
End Sub
Private Sub LoadLocalConfiguration()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub ConnectToDataBase()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadInterfaceObjects()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadClients()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
End Class
If all goes well, your splash screen should automatically display, update with progress, then automatically close when your main form has finished loading and displayed itself.
Me.Invoke(TempFunction, Value)
Should be:
Me.Invoke(TempFunction, new Object(){Value})
because the overload with parameters takes an array of parameters.
Value is on the stack of the function in the current thread. You need to allocate memory on the GC heap and copy the value to that memory so that it is available to the other thread even after the local stack has been destroyed.

Non modal form that blocks its Owner

I was trying to get forms configuration for my project that can suit my needs here and didn't get to a solution. It seem's I find a solution and now I have to implement it application-wide.
Here I have main form and a few subforms (which have many subforms) and through the main form I should ALWAYS be able to close all opened forms, watch for universal keypresses, close application permanently and watch for critical events.
I find solution in this facts:
From main form I open main subforms instantiated but non modally.
From sub forms I open deeper subforms also instantiated and non modally.
In all those subforms _Load event handler I disable calling form and in _FormClosing handler I enable it again. That way non modal forms act like modal forms in order to caller but not in order to main form which stays responsive all the time!
In forms that have to block its own caller (Owner) I added property "Blocking" so that my code looks like this:
If Not formIsOpened(frm_myFirstChild) Then
Dim f As New frm_myFirstChild
f.Blocking = True
f.Show(Me)
f = Nothing
End If
In the frm_myFirstChild I have property:
<Browsable(True), _
DefaultValue(False)> _
Public Property Blocking() As Boolean
Get
Return _Blocking
End Get
Set(ByVal value As Boolean)
_Blocking = value
End Set
End Property
If boolean property "Blocking" is TRUE then under _Load this code has to be executed:
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = False
End If
In _FormClosing that:
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
All of that work as expected so I try to implement that for all forms and use when needed in a subclass "cls_transform":
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
MyBase.OnFormClosing(e)
End Sub
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
If Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = False
End If
MyBase.OnLoad(e)
End Sub
Here I have problem that subclass doesn't understand the property Blocked (not declared).
How do you get a Form's property Blocked to subclass so I can use those subclasses for all forms, and switch the property Blocking from the outside regarding functionality needs?
Sounds to me that "cls_transform" is actually a class derived from Form, the only way that OnFormClosing could work. Which is okay, your "sub-forms" now need to derive from cls_transform instead of Form. Do pick a better name.
Then simply add the Blocking property to that cls_transform class to solve your problem.
Do note that there's a bug in your OnFormClosing method. It can be canceled and that will leave the form opened with its owner in the wrong state. You need to write it like this instead:
Protected Overrides Sub OnFormClosing(ByVal e As System.Windows.Forms.FormClosingEventArgs)
MyBase.OnFormClosing(e)
If Not e.Cancel And Blocking And Me.Owner IsNot Nothing Then
Me.Owner.Enabled = True
Me.Owner.Activate()
End If
End Sub

Raise an Event that changes a form from different class

I am wondering if it is possible to make and Raise an event from a class, to a form? I have a class that just loops through itself continually, but when a condition is met I want a button to become visible on a form. I am looking into different ways of accomplishing this. It is probably worth mentioning that I am multithreading and this loop is being run in a different thread than the UI is at. Which is why I am hoping it is possible to raise an event and that will jump over to the UI thread and make the button visible then come back to where it was in the loop.
Any suggestions or direction is appreciated.
Something like this would work,
Public Class MyClass
Public Event MyEvent()
Sub DoStuff()
RaiseEvent MyEvent
End Sub
Public Class
Public Class MyForm
Public Sub HandleEvent()
'can be called from another thread, so use invoke
Me.Invoke(Sub() MyButton.Visible = true )
End Sub
End Class
Dim mMyClass = New MyClass
Dim mMyForm = New MyForm
AddHandler mMyClass.MyEvent, AddressOf mMyForm.HandleEvent()
As long as you remember to use .Invoke when updating the UI if your event is coming from another thread, there is no issue with having a form handle events from any source.
I think here is a possible solution:
1) pass parent form to the class as a parameter;
2) in parent form, invoke the function
Private Delegate Sub DoStuffDelegate()
Private Sub DoStuff()
If Me.InvokeRequired Then
Me.Invoke(New DoStuffDelegate(AddressOf DoStuff))
Else
btn.Visible = true
End If
End Sub
3) in class, call it as parentform.DoStuff()
check the syntax as I typed it.

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.