Creating a form in a new thread (From an Event) - vb.net

I have a small form that notifies the user on the completion of events (such as SMO restore).
I want this form to appear from various sources (such as the below SMO Restore complete event) so I guess I need to create a new thread before creating the form? As it could be called from outside the UI thread. (I also need to pass a string to this form)
The child form fades in out using a timer + Opacity.
What am I doing wrong here?
Private Sub CompleteEventHandler(ByVal sender As Object, ByVal e As Microsoft.SqlServer.Management.Common.ServerMessageEventArgs)
MyThread = New System.Threading.Thread(AddressOf DoStuff)
MyThread.Start("meh")
End Sub
Private Delegate Sub DoStuffDelegate(ByVal MsgString As String)
Private Sub DoStuff(ByVal MsgString As String)
If Me.InvokeRequired Then
Me.Invoke(New DoStuffDelegate(AddressOf DoStuff))
Else
Dim TempMessage As New frmNotification
TempMessage.lblMessage.Text = MsgString
TempMessage.Show()
End If
End Sub

Don't start a new thread, there's no point since you're already running on another thread and InvokeRequired will always be True. The mistake is that you call Me.Invoke() but forget to pass the "MsgString" argument. You'll also want to use Me.BeginInvoke(), no need to wait. Thus:
Private Sub CompleteEventHandler(ByVal sender As Object, ByVal e As EventArgs)
Me.BeginInvoke(New DoStuffDelegate(AddressOf DoStuff), "meh")
End Sub
Private Sub DoStuff(ByVal MsgString As String)
Dim TempMessage As New frmNotification
TempMessage.lblMessage.Text = MsgString
TempMessage.Show()
End Sub

Related

Replacement for thread.start() and thread.abort()

I need to display a form for some amount of time - basically a "please wait, loading" form with progress bar. When certain operation completes, I want this window to disappear. Here's my try at it:
If IsNothing(mlLabels) Or mblnIsLoading Then Exit Sub
If mstrPrinterA.Equals(Me.cmbPrinters.Text, StringComparison.OrdinalIgnoreCase) Then
Exit Sub
End If
Dim th As New Threading.Thread(AddressOf WaitPrinter)
th.Start()
If mlLabels.IsPrinterOnLine(Me.cmbPrinters.Text) Then
Me.cmbPrinters.BackColor = Drawing.Color.Green
Else
Me.cmbPrinters.BackColor = Drawing.Color.Red
End If
th.Abort()
Do While th.IsAlive
Loop
th = Nothing
mstrPrinterA = Me.cmbPrinters.Text
Private Sub WaitPrinter()
Dim fw As New FormWaiting
fw.ShowDialog()
fw = Nothing
End Sub
However, I then read that using Thread.Start() and Thread.Abort() is not considered a good practice. Is there another way I can do that?
Here is a simple example of what I described in my comment above. Create a WinForms project with two forms, adding a Button to Form1 and a BackgroundWorker to Form2. Add this code to Form1:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Display a dialogue while the specified method is executed on a secondary thread.
Dim dialogue As New Form2(New Action(Of Integer)(AddressOf Pause), New Object() {5})
dialogue.ShowDialog()
MessageBox.Show("Work complete!")
End Sub
Private Sub Pause(period As Integer)
'Pause for the specified number of seconds.
Threading.Thread.Sleep(period * 1000)
End Sub
and this code to Form2:
Private ReadOnly method As [Delegate]
Private ReadOnly args As Object()
Public Sub New(method As [Delegate], args As Object())
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.method = method
Me.args = args
End Sub
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
BackgroundWorker1.RunWorkerAsync()
End Sub
Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'Execute the specified method with the specified arguments.
method.DynamicInvoke(args)
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
'Close the dialogue when the work is complete.
Close()
End Sub
Run the project and click the Button on the startup form. You'll see the dialogue displayed while the work is executed and then disappear when it's done. The dialogue is written in such a way that it can be used to invoke any method with any arguments. It's the caller that gets to define what the work to be performed is.
In this particular case, the "work" is a simple sleep but you can put anything you like in there. Just note that it is executed on a secondary thread so no direct interaction with the UI is allowed. If you need UI interaction then that could be accomplished but you'd need slightly more complex code. Note that the code as it is also does not allow for returning a result from the executed method, but you could support that fairly easily too.

backgroundworker throws "An error occurred creating the form..."

I have main forms that has a button that opens a master-customer on datagrid. I use dataview for the purpose of filtering the data using dataview.rowfilter.
The problem is, during the form load. It takes 5-6 seconds (the program is unresponsive during that time). What I'm trying to do is to load the data to the dataview on the background and show it on the gridview on workercompleted.
it gave me this error: "An error occurred creating the form. See Exception.InnerException for details. The error is: Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it." --> on dowork
I read somewhere that i should use Invoke. But i don't know how to use it.
here is my code:
Private Sub custcall_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
TBfind.Enabled = False
SetMyCustomFormat("yyyy-MM-dd HH:mm:ss")
BWcustload.RunWorkerAsync()
End Sub
Private Sub BWcustload_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles BWcustload.DoWork
mydataview = New DataView(datatablecust)
End Sub
Private Sub BWcustload_RunWorkerCompleted(ByVal sender As System.Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BWcustload.RunWorkerCompleted
DGVcustomer.DataSource = mydataview
TBfind.Enabled = True
End Sub
Have you tried moving the code to the event Form_Shown?
Also, have you tried putting the code in your DoWork section inside a SyncLock?
Try this:
SyncLock mydataview
mydataview = New DataView(datatablecust)
End SyncLock
As for the STA model ... try adding this to your code and set the startup object to Sub Main()
<STAThread()> _
Public Shared Sub Main()
Dim mainForm As New custcall()
Application.Run(mainForm)
End Sub
EDITED:
As far as invoke is concerned. ... that would definitely work, too ... but I don't know that it would have solved the STAThread issue.
To use invoke, first you have to declare a delegate sub in your form:
Delegate Sub LoadDataCallback()
Declare a function to take care of the actual loading of the data:
Private Sub LoadData()
mydataview = New DataView(datatablecust)
End Sub
Then, you would start a new thread in your Shown event (or Load event):
Dim mythread As New Thread(Sub()
Dim callLoad As New LoadDataCallBack(LoadData)
Me.Invoke(callLoad)
End Sub)
mythread.Start()
This gets around having to use SyncLock (although in some cases you may want to if it doesn't end up working correctly).
Don't forget to add Imports System.Threading to the top of your code file.

Update GridView by a BackgroundWorker Completion makes Grid non interactive

I am trying to update GridView on completion of a BackgroundWorker, For the first time it works correctly, but if try execute worker again, data will be assigned to the grid but i could not select a Row on the GridView on UI level and also vertical scroll is now shown. If try to double click cells several times then vertical scroll will appear and i could select any row.
Please refer the VB.Net Code
Public Class Form1
Dim Workers() As BackgroundWorker
Dim dtCustomers As DataTable = New DataTable()
Private dtCustomersLock As New Object
Private dgvCustomersLock As New Object
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
dtCustomers.Columns.Add("CustomerID")
dtCustomers.Columns.Add("CustomerName")
dtCustomers.Columns.Add("Age")
LoadWorkers()
End Sub
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
btnClear_Click(sender, e)
loadCustomerGrid()
UpdateCustomerGrid()
End Sub
Private Sub LoadWorkers()
ReDim Workers(1)
Workers(1) = New BackgroundWorker
Workers(1).WorkerReportsProgress = True
Workers(1).WorkerSupportsCancellation = True
AddHandler Workers(1).DoWork, AddressOf loadCustomerGrid
AddHandler Workers(1).RunWorkerCompleted, AddressOf UpdateCustomerGrid
End Sub
Private Sub btnLoadThread_Click(sender As Object, e As EventArgs) Handles btnLoadThread.Click
If Not Workers(1).IsBusy Then
dtCustomers.Clear()
Workers(1).RunWorkerAsync()
End If
End Sub
Private Sub loadCustomerGrid()
SyncLock dgvCustomersLock
For i As Integer = 0 To 10
dtCustomers.Rows.Add(i, "Customer" + i.ToString(), "20" + i.ToString())
Next
End SyncLock
Threading.Thread.Sleep(100)
End Sub
Private Sub UpdateCustomerGrid()
SyncLock dtCustomersLock
DataGridView1.DataSource = dtCustomers
DataGridView1.Focus()
End SyncLock
End Sub
Private Sub btnClear_Click(sender As Object, e As EventArgs) Handles btnClear.Click
dtCustomers.Clear()
End Sub
End Class
Because you are accessing the DataGridView1 of UI thread from the Worker thread you get the weird behaviour.
I tested your small app with this code and I got the normal expected behaviour.
I modified your loadCustomerGrid method and added another Method and Delegate method.
Private Sub loadCustomerGrid()
SetDataGrid(GridView1)
Threading.Thread.Sleep(100)
End Sub
Private Sub setDataGrid(ByVal grd As DataGridView)
If grd.InvokeRequired Then
grd.Invoke(New setDataGridInvoker(AddressOf setDataGrid), grd)
Else
For i As Integer = 0 To 10
dtCustomers.Rows.Add(i, "Customer" + i.ToString(), "20" + i.ToString())
Next
End If
End Sub
Private Delegate Sub setDataGridInvoker(ByVal grd As DataGridView)
Explanation:
"The way to safely access controls from worker threads is via delegation. First you test the InvokeRequired property of the control, which will tell you whether or not you can safely access the control. InvokeRequired is one of the few members of the Control class that is thread-safe, so you can access it anywhere. If the property is True then an invocation is required to access the control because the current method is executing on a thread other than the one that owns the control's Handle.
The invocation is performed by calling the control's Invoke or BeginInvoke method. You create a delegate, which is an object that contains a reference to a method. It is good practice to make that a reference to the current method. You then pass that delegate to the Invoke or BeginInvoke method. That will essentially call the referenced method again, this time on the thread that owns the control's Handle."
Source: jmcilhinney post Accessing Controls from Worker Threads http://www.vbforums.com/showthread.php?498387-Accessing-Controls-from-Worker-Threads

Open a modal form from a background thread to block UI thread without also blocking background thread

Maybe this is a simple question and I just don't know the correct search terms to find the answer, but my Google-fu has failed me on this one.
My vb.net application has a background thread that controls all the socket communication. Occasionally, I need this communication thread to open up a modal form to display a message and block UI interaction until the communication thread completes a series of tasks at which point, the communication thread will remove the modal form, allowing the user to continue interaction.
Currently, my communications class containing the background thread has two events, StartBlockingTask and EndBlockingTask. My main form has event listeners for these events that call like-named subs. They call code looking like this:
Private Delegate Sub BlockingDelegate(ByVal reason As String)
Private Sub StartBlockingTask(ByVal reason As String)
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
Me.Invoke(del, New Object() {reason})
Else
Try
_frmBlock.lblBlock.Text = reason
_frmBlock.ShowDialog()
Catch ex As Exception
'stuff
End Try
End If
End Sub
Private Sub EndBlockingTask()
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
Me.Invoke(del, New Object() {""})
Else
Try
If (Not _frmBlock Is Nothing) Then
_frmBlock.DialogResult = Windows.Forms.DialogResult.OK
End If
Catch ex As Exception
'stuff
End Try
End If
End Sub
This successfully blocks the UI from interaction, but it also blocks the communications thread so the EndBlockingTask event never actually gets raised. How can I open this modal dialog from the communications thread and allow the communications thread to still continue running?
Thanks in advance!
I disagree.
All that needs to be done is to change Invoke() to BeginInvoke() and you're golden.
This is because Invoke() is actually synchronous which causes it block until ShowDialog() resolves.
Using BeginInvoke() makes it asynchronous and allows the UI to be blocked while the thread continues:
Public Class Form1
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
If Not BackgroundWorker1.IsBusy Then
BackgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Delegate Sub BlockingDelegate(ByVal reason As String)
Private Sub StartBlockingTask(ByVal reason As String)
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
Me.BeginInvoke(del, New Object() {reason})
Else
Try
_frmBlock.lblBlock.Text = reason
_frmBlock.ShowDialog()
Catch ex As Exception
'stuff
End Try
End If
End Sub
Private Sub EndBlockingTask()
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
Me.BeginInvoke(del, New Object() {""})
Else
Try
If (Not _frmBlock Is Nothing) Then
_frmBlock.DialogResult = Windows.Forms.DialogResult.OK
End If
Catch ex As Exception
'stuff
End Try
End If
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
For i As Integer = 1 To 10
BackgroundWorker1.ReportProgress(i)
System.Threading.Thread.Sleep(1000)
If i = 4 Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
del("bada...")
ElseIf i = 7 Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
del("bing!")
End If
Next
End Sub
Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
Label1.Text = e.ProgressPercentage
End Sub
End Class
You are calling the address from within the sub it is created in. The Address needs to be called from outside this sub.
Private Sub StartBlockingTask(ByVal reason As String)
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf StartBlockingTask)
Private Sub EndBlockingTask()
If Me.InvokeRequired Then
Dim del As New BlockingDelegate(AddressOf EndBlockingTask)
You need to create two delegates. One for StartBlockingTask and one for EndBlockingTask
This is an example from MSDN,
Delegate Sub MySubDelegate(ByVal x As Integer)
Protected Sub Test()
Dim c2 As New class2()
' Test the delegate.
c2.DelegateTest()
End Sub
Class class1
Sub Sub1(ByVal x As Integer)
MessageBox.Show("The value of x is: " & CStr(x))
End Sub
End Class
Class class2
Sub DelegateTest()
Dim c1 As Class1
Dim msd As MySubDelegate
c1 = New Class1()
' Create an instance of the delegate.
msd = AddressOf c1.Sub1
msd.Invoke(10) ' Call the method.
End Sub
End Class
http://msdn.microsoft.com/en-us/library/5t38cb9x(v=vs.71).aspx
Let me know if this helps.

How to raise events from class library to form using module?

i've a app that starts from a sub in a module, do a few things, and then load the form.
But it doesn't work :/
Here we execute dBase.AddTemporalFilepath
module.vb
Public dBase As New Core.clsDatabase
Public Sub Main()
FurBase.Directory = My.Application.Info.DirectoryPath
If appMutex.WaitOne(TimeSpan.Zero, True) Then
ShowUploader()
End If
Dim returnValue As String()
returnValue = Environment.GetCommandLineArgs()
If returnValue.Length > 1 Then
If My.Computer.FileSystem.FileExists(returnValue(1).ToString) Then
dBase.AddTemporalFilepath(returnValue(1).ToString)
End If
End If
End Sub
Private Sub ShowUploader()
Application.EnableVisualStyles()
Application.Run(frmUploader)
End Sub
We raise the event TempFilepathAdded
clsDatabase.vb
Public Class clsDatabase
Public Event TempFilepathAdded()
Public Function AddTemporalFilepath(ByVal filepath As String)
...
RaiseEvent TempFilepathAdded()
...
End Function
End Class
We catch the event
form.vb
Private Sub form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
AddHandler dBase.TempFilepathAdded, AddressOf TempFilepathAddedHandler
End Sub
Private Sub TempFilepathAddedHandler()
MsgBox("Event raised")
End Sub
Any Idea?
More info:
The event is raised when the form is closed.
The line "Application.Run(frmUploader)" pauses your program until the Window closes. Basically it hijacks the main thread to handle stuff like users clicking buttons.
Normally your Main function should look like this:
Setup
Application.Run
Clean-up
Sorry, but it looks like its time to reorganize your code.