WaitGif Form - Coordinated cancellation of thread (via token) with Cancel Button in Spawned Form - vb.net

I am using a class to load a waitgif form (via threading), attributed to #nobugz here. The spawned waitgif form has a cancel button. The problem is that I need the class that spawned the thread (waitgif form) to listen for a cancelled token from the spawned form. So can I feed the token to the threaded waitgif form via a property, so that upon cancellation the parent thread will terminate?
Also, as a general question, whenever t.Source.Cancel is used in a method of an instantiated class, can it be followed by Me.Dispose based on the declaration Implements IDisposable to speed up memory availability without forcing garbage collection?
Public Class PleaseWait
Implements IDisposable
Private mSplash As New Form
Private mLocation As Point
Shared t As New TokenPOC()
Sub New(ByVal location As Point)
mLocation = location
ThreadPool.QueueUserWorkItem(Sub(state) workerThread(), t.Token)
End Sub
Public Sub Dispose()
If mSplash.IsHandleCreated Then
mSplash.Invoke(New MethodInvoker(AddressOf stopThread))
End If
End Sub
Private Sub stopThread()
mSplash.Close()
End Sub
Private Sub workerThread()
mSplash = Form_WaitGif
mSplash.StartPosition = FormStartPosition.Manual
mSplash.Location = mLocation
mSplash.TopMost = True
Application.Run(mSplash)
End Sub
End Class
Public Class Form_WaitGif
Private Sub Form_WaitGif_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.CenterToScreen()
LoadDataFormAbort = 0
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
t.Source.Cancel() 'Not recognized
Me.Dispose() 'Is this needed when killing large classes to free up memory without forced GC?
Label1.Left = 100
Label1.Text = "Please wait - Terminating data loading (may take a while for large files...)"
End Sub
Public Overloads Sub Dispose() Implements IDisposable.Dispose
'Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class

Related

Change UI parameters using thread in separate class

I would like to be able to change an element of the main form inside a thread declared in a separated class (In this case I want to change a label text).
I tried the following code:
Form1:
Imports System.Threading
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim SecondClassObject As New SecondClass()
End Sub
End Class
SecondClass:
Imports System.Threading
Public Class SecondClass
Public Thread As New Thread(AddressOf Increment)
Public counter As Integer = 0
Sub New()
Thread.Start()
End Sub
Sub Increment()
While True
Form1.Label1.Text = counter
counter += 1
End While
End Sub
End Class
If I do the same thing using a thread but in the form code itself than the label text will change:
Imports System.Threading
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim thread As New Thread(AddressOf Increment)
thread.Start()
End Sub
Sub Increment()
While True
Label1.Text = counter
counter += 1
End While
End Sub
End Class
How should I do in order to archieve the same result using a thread in a separated class?
First, I do want to point out that you need to use an Invoke/Callback to safely set the label's text from the secondary thread. I don't know if you're doing that in your actual code base, but wanted to specify anyways.
Now, focused on the actual question, I believe that the easiest way to do as requested is to pass a reference to the original instance of Form1 to your SecondClass. Having a reference to the parent, means that you would be able to manipulate the parent's publicly exposed elements as needed.
Consider the below:
Public Class Form1
Public counter As Integer = 0
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim secondClass As New SecondClassObject(me)
End Sub
Delegate Sub SetTextCallback (value as String)
Public Sub SetText (value as string)
if me.Label1.InvokeRequired Then
dim d as New SetTextCallback(addressOf SetText)
Me.Invoke(d, New Object() {value})
Else
me.label1.text = value
End If
End Sub
End Class
Public Class SecondClassObject
private _parent as Form1
private myThread As New Thread(AddressOf Increment)
Public Sub New (byref p as Form1)
me._parent = p
myThread.Start()
End Sub
Sub Increment()
While True
Me._parent.SetText(counter)
counter += 1
End While
End Sub
End Class
What is happening is that the a reference to the parent is passed into the second class as a constructor, doing so allows us to interact with the parent from the second class.
Now, that is one way, but other options do exist. Things such as specialized events/handlers or wiring up databinding between the Form1.Label1 and a property exposed from the SecondClassObject. Even a singleton pattern, where the value to be incremented is shared between all instances, so when the SecondClassObject increments it, Form1 would be aware and know to update Label1.
Also, please note that the above code is for example purposes, and is missing things such as a defined declaration for Label1.
On windows it's not possible to change the UI from a non UI thread.
It looks like that you have to use Control.Invoke or better Control.BeginInvoke.
The problem with using Control.Invoke is that it's executed on the UI thread and the calling thread waits for completion. Which would be bad when your background worker continusly does some computations.

Best Practices - Form Class Receiving Message from Class Modules

Hoping to get some best-practice advise with regards to capturing a returned message from an instantiated class on my form.
In my form (form1.vb), I have a label which reflects what is being done, with the code below.
Code in form1.vb to display message:
Public Sub DisplayMessage(ByVal Msg as String, ByVal Show as Boolean)
Application.DoEvents()
If Show Then
lblShow.Text = Msg
lblShow.Refresh()
End If
End Sub
I have came across three methods so far:
Direct Form Call. In this scenario the class directly calls the form's message routine:
form1.DisplayMessage("Show This Message", True)
RaiseEvent within class. In this scenario form1 is Friends WithEvents of the class sending the message, and the class raises the event to the form.
**Declared in Form1.vb**
Friend WithEvents Class1 as New Class1
**Declared in Class1.vb**
Public Event SetMessage(ByVal Msg As String, ByVal Show As Boolean)
**Used in Class1.vb**
RaiseEvent SetMessage("Show This Message", True)
Have an EventArgs class handle the event. In this scenario we have an EventArg.vb class which is instantiated whenever we raise the event.
**Declared in Form1.vb**
Friend WithEvents Class1 as New Class1
Private Sub class1_DisplayMessage(ByVal Msg As String, ByVal showAs Boolean, ByRef e As ProgressMessageEventArgs) Handles Class1.SetMessage
DisplayMessage(Msg, Show)
End Sub
**Declared in Class1.vb**
Public Event SetMessage(ByVal msg As String, ByVal Show As Boolean, ByRef e As ProgressMessageEventArgs)
Protected Sub CaptureMessage(ByVal msg As String, ByVal Show As Boolean)
RaiseEvent SetMessage(message, ShowList, New ProgressMessageEventArgs(message))
End Sub
**Used in Class1.vb**
RaiseEvent CaptureMessage("Show This Message", True)
**EventArg.vb created to handle ProgressMessageEventArgs class**
Public NotInheritable Class ProgressMessageEventArgs
Inherits System.EventArgs
Public txt As String
Public Sub New(ByVal txt As String)
MyBase.New()
Me.Text = txt
End Sub
End Class
Scenario 1 is seemingly the simplest, though I was advised against this and asked to raise an event instead. Over time I came across scenario 3 which involves an additional class vs scenario 2.
Therefore, the question is...
Between these three methods, which would be the "proper" way of returning a message from a class to the form? Is the additional EventArg class as per scenario 3 necessary since scenario 2 works fine as well?
Many thanks in advance.
My answer is none of the above. Consider this example
Public Class Form1
Private WithEvents myClass1 As New Class1()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
myClass1.CountTo1000()
End Sub
Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
Me.Label1.Text = number.ToString()
End Sub
End Class
Public Class Class1
Public Event Updated(number As Integer)
Public Sub CountTo1000()
For i = 1 To 1000
System.Threading.Thread.Sleep(1)
RaiseEvent Updated(i)
Next
End Sub
End Class
You have a form and a class, and the form has a reference to the class (the class doesn't even know the form exists). Your business logic is performed in the class, and the form is used to input and display information. CountTo1000() is being called directly from the form, which is bad because basically the UI thread is being put to sleep 1000 times, while the class is trying to update the UI by raising the event after each sleep. But the UI never has time to allow the events to happen, i.e. to be updated. Placing an Application.DoEvents() after Me.Label1.Text = number.ToString() will allow the UI to update. But this is a symptom of bad design. Don't do that.
Here is another example with multi-threading
Public Class Form1
Private WithEvents myClass1 As New Class1()
' this handler runs on UI thread
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' make a new thread which executes CountTo1000
Dim t As New System.Threading.Thread(AddressOf myClass1.CountTo1000)
' thread goes off to do its own thing while the UI thread continues
t.Start()
End Sub
' handle the event
Private Sub MyClass1_Updated(number As Integer) Handles myClass1.Updated
updateLabel(number.ToString())
End Sub
' invoke on UI thread if required
Private Sub updateLabel(message As String)
If Me.Label1.InvokeRequired Then
Me.Label1.Invoke(New Action(Of String)(AddressOf updateLabel), message)
Else
Me.Label1.Text = message
End If
End Sub
End Class
Public Class Class1
Public Event Updated(number As Integer)
Public Sub CountTo1000()
For i = 1 To 1000
System.Threading.Thread.Sleep(1)
RaiseEvent Updated(i)
Next
End Sub
End Class
This simple example shows how a thread can be created and run some code off the UI. When doing this, any method call from the non-UI thread must be invoked on the UI if it must access a UI control (Label1). The program runs smoothly since the Thread.Sleep is done on a different thread than the UI thread, with no need for Application.DoEvents, because the UI thread is otherwise doing nothing, and can handle the events being raised by the other thread.
I focused more on threading, but in both examples the design has a form with a class, and the form knows about the class, but the class doesn't know about the form. More about that can be seen here.
See also:
Why we need to check for InvokeRequired, then invoke: Control.InvokeRequired
A better option than Thread nowadays: BackgroundWorker
An even cooler option, if you can wrap your head around it: Async/Await

Handling a timer from a class

I want some values in a class to decrease whenever the timer in the main form ticks. I am creating multiple instances of the same class as my program is a simulation application and I am not storing these instances in an array or any list in that matter. I simply declare them and add their picture box to the controls on the main form. However I am hoping to have a sub routine inside the class that triggers whenever the timer in the main form ticks. I thought of something like this:
Public Class Jimmy
Dim _a As Integer = 10
Sub decreseNum(sender As Object, e As EventArgs) Handles mainapp.tmrLog.Tick
_a -= 1
End Sub
End Class
with mainapp being the name of the main form and tmrLog being the timer I want to associate my sub routine with. However the above code doesn't work
You could try defining a local reference to the timer in the Jimmy class:
Public Class Jimmy
Dim _a As Integer = 10
Private WithEvents tmr As Timer
Public Sub New(ByRef MainTmr As Timer)
tmr = MainTmr
End Sub
Sub decreseNum(sender As Object, e As EventArgs) Handles tmr.Tick
_a -= 1
End Sub
End Class
If you want all your classes react to timer.elapsed event, just sign up for it. The program below is fully operational. It is example what you can do to have your children to react to timer events of single parent/timer
Imports System
imports system.timers
Public Module Module1
Public Sub Main()
dim mc as new MainClass()
mc.CreateChildren(5)
System.Threading.Thread.Sleep(60000) ' wait and monitor output of childern
mc.Stop()
Console.WriteLine("All should stop now...")
Console.Read()
End Sub
End Module
public class MainClass 'This class could be your form
private _timer as new Timer(5000)
public sub CreateChildren(count as integer)
For i as integer = 1 to count
dim c as new Child(i)
Addhandler _timer.Elapsed, addressof c.DoWhentimerTicks
next
Console.WriteLine("timer should run now...")
_timer.Start()
end sub
public sub [Stop]()
_timer.Stop()
End Sub
End class
public class Child
private _myNO as integer
public sub new (no as integer)
_myNo = no
end sub
public sub DoWhentimerTicks(sender as object , e as ElapsedEventArgs)
Console.WriteLine(string.format("Child #{0} just ticked. Time = {1}", _myNo, e.signaltime))
end sub
End class
I found my solution, posting here for further reference.
My situation was trying to have my timer in the mainform triggering a sub in a class, and I used the following solution.
Class:
Sub addHandlesToSub
AddHandler Form1.Timer1.Tick, AddressOf subToBeTriggered
End Sub
Sub subToBeTriggered(sender As Object, e As EventArgs)
'My code
End Sub
The parameters in subToBeTriggered are useful when you want to remove the handler with
RemoveHandler Form1.Timer1.Tick, AddressOf subToBeTriggered
Otherwise, there will be an error without the parameters.
Thanks for all the answers though.

VB.Net Inheritance Override

I'm new to VB.Net (I'm from a foxpro background) and have had my head in a book for the last two weeks trying to get started with some of the basics.
I'm trying to master class inheritance and have what I hope is not too much of a challenging question.
I've created a class and compiled it as a DLL. It simply allows me to place a button on a form. I just want to capture the Click event - which I've managed to do but would like to override the inherited code rather than having both fire which seems to be happening at the moment.
I realise I could just double click the control and enter code directly into the MyButton1 click event but wanted to trap this programmatically instead via the handler.
I thought this would just be a case of using the overridable / overrides options.
Here's the code in my class:
Imports System.Windows.Forms
Imports System.Drawing
Public Class MyButton
Inherits Windows.Forms.Button
Sub New()
End Sub
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
MsgBox("Base Click")
End Sub
End Class
Then I place the button on my form and name it MyButton1 and in the load event:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
AddHandler MyButton1.Click, AddressOf Button_Click
End Sub
Private Sub Button_Click()
MsgBox("Actual Click")
End Sub
Problem is, both events fire and I want the option to override / turn off the base event.
I thought I could just add the 'overrides' keyword to the Button_Click routine i.e.:
Private Sub Overrides Button_Click()
but I get an error message Sub Button_Click() cannot be declared 'overrides' because it does not override a sub in a base class
So to clarify - at the moment my code fires both events so I get two messages. I want to be able to turn off / supress the base class event.
Any help would be much appreciated.
I thought this would just be a case of using the overridable /
overrides options.
The fundamental problem here is that you're trying to push a square peg into a round hole.
To override something, you need to have inheritance involved. The derived class is overriding something that was inherited from the base class. For instance, if you inherited from your MyButton class to create a new type of Button called MyButtonDerived, then you could do it as expected:
Public Class MyButton
Inherits Windows.Forms.Button
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
MsgBox("Base Click")
End Sub
End Class
Public Class MyButtonDerived
Inherits MyButton
Protected Overrides Sub MyButton_Click(sender As Object, e As EventArgs)
' We don't call the base method...
' MyBase.MyButton_Click(sender, e)
' ... and instead do something else:
MsgBox("Derived Click")
End Sub
End Class
In contrast, when you've placed MyButton onto the Form as in your original problem description, no inheritance has taken place. Instead what you've setup is "object composition"; the form contains an instance of the button (not derived from it). While it may be possible to change what happens when the button is clicked from the form itself, this is not a case that can be solved with OOP, inheritance and overriding.
If MyButton was not designed in such a way that allows the end user to suppress its base functionality, then your options are limited in how you can use it. Here is an example of what it might look like if MyButton was designed to allow the end user to suppress its base click functionality:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
MyButton1.SuppressDefaultClick = True
End Sub
Private Sub MyButton1_Click(sender As Object, e As EventArgs) Handles MyButton1.Click
MsgBox("Form Click Code")
End Sub
End Class
Public Class MyButton
Inherits Windows.Forms.Button
Private _suppress As Boolean = False
Public Property SuppressDefaultClick As Boolean
Get
Return _suppress
End Get
Set(value As Boolean)
_suppress = value
End Set
End Property
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
If Not SuppressDefaultClick Then
MsgBox("Base Click")
End If
End Sub
End Class
If MyButton didn't include a way to suppress its built-in click handler like above then you'd have to resort to other means to solve your problem. In that case you'd have to prevent the button from ever receiving the message that the left mouse button has been clicked at all, and instead implement your own routine. This approach would be a considered a hack, since you are working around the limitations of something and not using it in the way it was originally intended. Here's one way the hack could be implemented:
Public Class Form1
Private WithEvents TMBC As TrapMyButtonClick
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TMBC = New TrapMyButtonClick(Me.MyButton1)
End Sub
Private Sub TMBC_Click(sender As MyButton) Handles TMBC.Click
MsgBox("Form Click Code")
End Sub
Private Class TrapMyButtonClick
Inherits NativeWindow
Private _mb As MyButton
Private Const WM_LBUTTONDOWN As Integer = &H201
Public Event Click(ByVal sender As MyButton)
Public Sub New(ByVal mb As MyButton)
If Not IsNothing(mb) AndAlso mb.IsHandleCreated Then
_mb = mb
Me.AssignHandle(mb.Handle)
End If
End Sub
Protected Overrides Sub WndProc(ByRef m As Message)
Select Case m.Msg
Case WM_LBUTTONDOWN
RaiseEvent Click(Me._mb) ' raise our custom even that the form has subscribed to
Exit Sub ' Suppress default behavior
End Select
MyBase.WndProc(m)
End Sub
End Class
End Class
Public Class MyButton
Inherits Windows.Forms.Button
Protected Overridable Sub MyButton_Click(sender As Object, e As System.EventArgs) Handles Me.Click
MsgBox("Base Click")
End Sub
End Class
You are getting 2 messages because you have set 2 different event handlers for the Click event:
The MyButton_Click method defined in your MyButton class.
The Button_Click method set in your AddHandler call on the form.
As noted in a comment above, you need to override the Button.OnClick method in your MyButton class instead of creating a new method:
Imports System.Windows.Forms
Imports System.Drawing
Public Class MyButton
Inherits Windows.Forms.Button
Sub New()
End Sub
' Override the OnClick event defined in "Button" class.
Protected Overrides Sub OnClick(e As System.EventArgs)
' Call the Click event from "Button" class.
MyBase.OnClick(e)
' Some custom events.
MsgBox("MyButton Click")
End Sub
End Class
It might be a good exercise to set breakpoints in the Button_Click and MyButton.OnClick methods so you can see exactly how the stack is created.

Can't Remove Listview Item From Second Form

I am having an issue when trying to delete ListView Items from a second form.
For example, if I use the following command on Form1 it works:
Listview1.SelectedItems(0).Remove
However, if I attempt to remove from Form2 like so:
Form1.Listview1.SelectedItems(0).Remove
I get the following error:
"Invalid argument=value of '0' is not valid for 'index'. Parameter name: index"
I then tried to get a count of items from the listview on Form2 and it gives me a return of 0
Form1.Listview1.Items.Count
I'm not sure what my problem is.
Update
I have posted a brief example of my code (using your suggestion as I can understand it):
frmShowMessages
Private Sub ViewMessage()
Dim frm As New frmViewMailMessage
frm.Show()
End Sub
Public Sub DeleteItem(ByVal index As Integer)
lsvReceivedMessages.Items(index).Remove()
End Sub
frmViewMessage
Private instanceForm as frmShowMessages
Private Sub frmViewMailMessage_Load(sender As Object, e As EventArgs) Handles MyBase.Load
instanceForm = New frmShowMessages()
End Sub
Private Sub cmdDelete_Click(sender As Object, e As EventArgs) Handles cmdDelete.Click
instanceForm.DeleteItem(_index)
End Sub
Hopefully my code can help identify where my issue is.
In VB.net usually you get a default Form instance for each of your Form. Probably you are creating an instance of Form1 and then you are trying to access ListView1 of default instance.
E.g.
Sub ButtonClick()
Dim f As New Form1()
f.Show()
' at this point if you access f's ListView you will get correct count
f.ListView1.Items.Count
' however if you try to access default instance it will NOT have any item
Form1.ListView.Items.Count
End Sub
It means your instance f is NOT equal to default Form1 instance.
Solution can be, make the f variable as class level variable and use it everywhere. Or if Form1 will have only 1 instance, then you can use the default instance everywhere.
Personally I would NOT go with direct control accessing over forms. I would create a Public method which should return the data as list to the caller, in this case your Form2.
UPDATED-2:
As per your given scenario, I am simplifying things for you, and doing implementation using Event.
Public Class frmShowMessages
Private Sub btnOpenMessage_Click(sender As System.Object, e As System.EventArgs) Handles btnOpenMessage.Click
Dim frmView As New frmViewMessage(Me.ListView1.SelectedItems(0).Index)
AddHandler frmView.MessageDeleted, AddressOf DeleteMessageHandler
frmView.Show()
End Sub
Private Sub DeleteMessageHandler(sender As Object, e As frmViewMessage.MessageDeletedEventArgs)
Me.ListView1.Items.RemoveAt(e.MessageIndex)
End Sub
End Class
Public Class frmViewMessage
' a class which will be used for Event communication
Public Class MessageDeletedEventArgs
Inherits EventArgs
Public Property MessageIndex As Integer
Public Sub New(ByVal iIndex As Integer)
MyBase.New()
Me.MessageIndex = iIndex
End Sub
End Class
' main event which will alert the parent that a message deletion should be done
Public Event MessageDeleted As EventHandler(Of MessageDeletedEventArgs)
' private variable that will hold the MessageIndex
Private Property MessageIndex As Integer
' method that is responsible to raise event
Protected Overridable Sub OnMessageDeleted()
RaiseEvent MessageDeleted(Me, New MessageDeletedEventArgs(Me.MessageIndex))
End Sub
' we want to create this Form using the MessageIndex of ListView
Public Sub New(ByVal iMessageIndex As Integer)
Me.InitializeComponent()
Me.MessageIndex = iMessageIndex
End Sub
' the delete button will raise the event to indicate parent that
' a deletion of message should be done
Private Sub btnDelete_Click(sender As System.Object, e As System.EventArgs) Handles btnDelete.Click
Me.OnMessageDeleted()
End Sub
End Class