Public Class ThreadWithState
' State information used in the task.
Private locationx As Integer
Private locationy As Integer
' The constructor obtains the state information.
Public Sub New(x As Integer, y As Integer)
locationx = x
locationy = y
End Sub
' The thread procedure performs the task, such as formatting
' and printing a document.
Dim win As New VBSoftKeyboard.KeyBoardForm
Public Sub ThreadProc()
'MessageBox.Show(locationy)
Console.WriteLine(locationx, locationy)
win.FormBorderStyle = Windows.Forms.FormBorderStyle.None
win.Location = New Point(locationx, locationy)
win.StartPosition = FormStartPosition.Manual
Application.Run(win)
End Sub
Public Sub HideWin()
win.Hide()
End Sub
Public Sub ShowWin()
win.Show()
End Sub
End Class
Dim tws As New ThreadWithState(1000, 0) 'show the keyboard form location
Private Sub SimpleButton1_Click_1(sender As Object, e As EventArgs) Handles SimpleButton1.Click
Dim t As New Thread(New ThreadStart(AddressOf tws.ThreadProc))
t.Start()
tws.HideWin() //PROBLEM HERE ! how to hide the form ? and when i want to use i can show the form
End Sub
I use a thread to create a form, and then i m trying to hide the form first, when want to use the form i only call the ShowWin() function. t as thread to start the thread and create the form, how to call the t thread HideWin() function?
How to use created thread to call the others function ?
Related
I'm working on a Visual Basic Project. My working environment is :
Windows 10 32bit
Visual Studio 2015
.Net Framework 4.8
Winform
At this stage,I have :
Class (Class1.vb)
Form1 (Form1.vb) with TreeView Control
I'm supposed to Scrape a webpage (i.e: https://www.example.com), I want to display the result of Scraping in a Treeview Control placed on Form1. I have tried some approaches and they worked fine, except that they require using Webbrowser Control which I do not wish to use. I found a method that I'm using now, but it seems to not letting me display the Results on the Form.
Here is my Code of Class1.vb and it's working fine
Imports System.Threading.Tasks
Public Class Class1
' Create a WebBrowser instance.
Private Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Private ManufacturersURi As New Uri("https://www.example.com/Webpage.php3")
Public ManList As New List(Of TreeNode)
Public Sub GettHelpPage()
' Create a WebBrowser instance.
Dim webBrowserForPrinting As New WebBrowser() With {.ScriptErrorsSuppressed = True}
' Add an event handler that Scrape Data after it loads.
AddHandler webBrowserForPrinting.DocumentCompleted, New _
WebBrowserDocumentCompletedEventHandler(AddressOf GetManu_Name)
' Set the Url property to load the document.
webBrowserForPrinting.Url = ManufacturersURi
End Sub
Private Sub GetManu_Name(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
Dim webBrowserForPrinting As WebBrowser = CType(sender, WebBrowser)
Dim Divs = webBrowserForPrinting.Document.Body.GetElementsByTagName("Div")
' Scrape the document now that it is fully loaded.
Dim T As Task(Of List(Of TreeNode)) =
Task.Run(Function()
Dim LinksCount As Integer = 0
For Each Div As HtmlElement In Divs
If InStr(Div.GetAttribute("ClassName").ToString, "Div-Name", CompareMethod.Text) Then
LinksCount = Div.GetElementsByTagName("a").Count - 1
For I As Integer = 0 To LinksCount
Dim Txt() As String = Div.GetElementsByTagName("a").Item(I).InnerHtml.Split("<BR>")
Dim Manu_TreeNode As New TreeNode() With
{.Name = I.ToString, .Text = Txt(0)}
ManList.Add(Manu_TreeNode)
Next
End If
Next
Return ManList
End Function)
' Dispose the WebBrowser now that the task is complete.
Debug.WriteLine(T.Result.Count) 'Result is 116
webBrowserForPrinting.Dispose()
End Sub
The above Code results 116 TreeNodes, which are the count of Tags that I scraped. Now when I attempt to display this result on Form1_Load, nothing happens, because the Form loads before the Code finishes executing.
Here is the Form1_Load Code :
Public Class Form1
Dim ThisClass As New Class1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ThisClass.GetHelpPage()
TreeView1.Nodes.Clear()
For I As Integer = 0 To ThisClass.ManList.Count - 1
TreeView1.Nodes.Add(ThisClass.ManList(I))
Next
End Sub
End Class
I noticed that if I placed an empty msgbox("") in the Form1_Load somewhere before For..Next, it forces the Form1_Load Event to wait and successfully populates the TreeView Control.
What am I doing wrong ? or What am I missing there ?
I noticed that if I placed an empty msgbox("") in the Form1_Load somewhere before For..Next, it forces the Form1_Load Event to wait and successfully populates the TreeView Control.
Yes, it plays the await role if you keep it open long enough until the task in the GetManu_Name method is completed. Since the MsgBox is a modal window which blocks the next lines from being executed until it been closed.
Now, either you make it a complete synchronous call by removing the Task.Run(...) from the GetManu_Name method, or utilize an asynchronous pattern in such a way as:
Public Class WebStuff
Public Shared Async Function ToTreeNodes(url As String) As Task(Of IEnumerable(Of TreeNode))
Dim tcsNavigated As New TaskCompletionSource(Of Boolean)
Dim tcsCompleted As New TaskCompletionSource(Of Boolean)
Dim nodes As New List(Of TreeNode)
Using wb As New WebBrowser With {.ScriptErrorsSuppressed = True}
AddHandler wb.Navigated,
Sub(s, e)
If tcsNavigated.Task.IsCompleted Then Return
tcsNavigated.SetResult(True)
End Sub
AddHandler wb.DocumentCompleted,
Sub(s, e)
If wb.ReadyState <> WebBrowserReadyState.Complete OrElse
tcsCompleted.Task.IsCompleted Then Return
tcsCompleted.SetResult(True)
End Sub
wb.Navigate(url)
Await tcsNavigated.Task
'Navigated.. if you need to do something here...
Await tcsCompleted.Task
'DocumentCompeleted.. Now we can process the Body...
Dim Divs = wb.Document.Body.GetElementsByTagName("Div")
Dim LinksCount As Integer = 0
For Each Div As HtmlElement In Divs
If Div.GetAttribute("ClassName").
IndexOf("Div-Name", StringComparison.InvariantCultureIgnoreCase) > -1 Then
LinksCount = Div.GetElementsByTagName("a").Count - 1
For I As Integer = 0 To LinksCount
Dim Txt = Div.GetElementsByTagName("a").Item(I).InnerHtml.
Split({"<BR>"}, StringSplitOptions.RemoveEmptyEntries)
Dim n As New TreeNode With {
.Name = I.ToString, .Text = Txt.FirstOrDefault
}
nodes.Add(n)
Next
End If
Next
End Using
Return nodes
End Function
End Class
Notes on the method:
A single Async function to do the lengthy task and returns IEnumerable(Of TreeNode) to the caller.
Lambda Expressions are used to add the WebBrowser.Navigated and WebBrowser.DocumentCompleted events.
The TaskCompletionSource is necessary here to wait for the completion of the WebBrowser.DocumentCompleted event in order to be able to process the HTML contents.
You need to add the Async modifier to the caller's signature to call the function and wait for the result. For example, the Form.Load event:
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim nodes = Await WebStuff.ToTreeNodes("www....")
TreeView1.Nodes.AddRange(nodes.ToArray)
End Sub
Or Async method:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
PopulateTree()
End Sub
Private Async Sub PopulateTree()
Dim nodes = Await WebStuff.ToTreeNodes("www....")
TreeView1.Nodes.AddRange(nodes.ToArray)
End Sub
Try this
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim ThisClass As New Class1
Dim i As Integer = 0
Do Until ThisClass.IsCompleted
Threading.Thread.Sleep(100)
'if the document takes too much time
i += 1
If i > 30 Then Exit Do 'more than 3 sec
Loop
TreeView1.Nodes.Clear()
For I As Integer = 0 To ThisClass.ManList.Count - 1
TreeView1.Nodes.Add(ThisClass.ManList(I))
Next
End Sub
End Class
Class Class1
Dim Completed As Boolean = False
ReadOnly Property IsCompleted As Boolean
Get
Return Completed
End Get
End Property
Private Sub GetManu_Name(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
'your code
Completed = True
Return ManList
End Sub
Issue
I am using multi-threading inside my application and the way it works is that i have an array that contains 22 string which stand for some file names:
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "FFCHLDP", "FFDBKDP", "FFDREQP", "FFINVHP", "FFJMNEP", "FFPIVHP", "FFUNTTP", "FJBJM1P", "FJBJM2P", "FJBNT2P", "FPPBE9P", "FTPCP1P", "FTTEO1P", "FTTRQ1P", "FJBJU1P", "FTTEG1P", "FFJACPP", "XATXTDP"}
I then loop through the array and create a new thread for each file:
For Each mThreadName As String In ThreadList
Dim mFileImportThread = New FileImportThreadHandling(mThreadName, mImportGuid, mImportDate, Directory_Location, mCurrentProcessingDate, mRegion)
Next
So inside the new thread 'FileImportThreadHandling' it will call a method by starting a new thread:
mThread = New Thread(AddressOf DoWork)
mThread.Name = "FileImportThreadHandling"
mThread.Start()
Then in 'DoWork' it will determine what file is current in question and will run the code related to the file.
After the code has ran for the file I want to report this back to the main thread. Can somebody give me a solution please.
You will need to use Delegates
EDIT:
Take a look at this snippet:
Sub Main()
mThread = New Thread(AddressOf doWork)
mThread.Name = "FileImportThreadHandling"
mThread.Start()
End Sub
Sub doWork()
'do a lot of hard work
workDone(result)
End Sub
Delegate Sub workDoneDelegate(result As Integer)
Sub workDone(abilita As Boolean, Optional src As Control = Nothing)
If Me.InvokeRequired Then
Me.Invoke(New workDoneDelegate(AddressOf workDone), {result})
Else
'here you're on the main thread
End If
End Sub
Here is an example that might give you some ideas. Note the use of Async and Task. You will need a form with two buttons, and a label.
Public Class Form1
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "*******", "FFJACPP", "XATXTDP"}
'note Async keyword on handler
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Button1.Enabled = False
Dim running As New List(Of Task)
For Each tskName As String In ThreadList
'start new task
Dim tsk As Task
tsk = Task.Run(Sub()
For x As Integer = 1 To 10
Dim ct As Integer = x
'simulate code related to the file
Threading.Thread.Sleep(500)
'report to the UI
Me.Invoke(Sub()
Label1.Text = String.Format("{0} {1}", tskName, ct)
End Sub)
Next
End Sub)
Threading.Thread.Sleep(100) 'for testing delay between each start
running.Add(tsk)
Next
'async wait for all to complete
For Each wtsk As Task In running
Await wtsk
Next
Button1.Enabled = True
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
'test UI responsive during test
Label1.Text = DateTime.Now.ToString
End Sub
End Class
Using Actions and a callback to track remaining operations. (Simplified your class for my example)
Public ThreadList As String() = {"FSANO1P", "FJBJB1P", "COPOR1P", "FFBIVDP", "FFCHLDP"}
Private actionCounter As Integer = 0
Private lockActionCounter As New Object()
Sub Main()
Console.WriteLine("Starting...")
For Each mThreadName As String In ThreadList
Dim mFileImportThread = New FileImportThreadHandling(mThreadName)
actionCounter += 1
Call New Action(AddressOf mFileImportThread.DoWork).
BeginInvoke(AddressOf callback, mThreadName)
Next
Console.Read()
End Sub
Private Sub callback(name As IAsyncResult)
Dim remainingCount As Integer
SyncLock lockActionCounter
actionCounter -= 1
remainingCount = actionCounter
End SyncLock
Console.WriteLine("Finished {0}, {1} remaining", name.AsyncState, actionCounter)
If remainingCount = 0 Then Console.WriteLine("All done")
End Sub
Private Class FileImportThreadHandling
Shared r = New Random()
Private _name As String
Public Sub New(name As String)
_name = name
End Sub
Public Sub DoWork()
Dim delayTime = (r).Next(500, 5000)
Console.WriteLine("Doing {0} for {1:0}ms.", _name, delayTime)
Thread.Sleep(delayTime)
End Sub
End Class
In order to keep responsiveness in the UI, I use a separate thread to execute various process, for example some FTP download.
Private Sub Button11_Click(sender As Object, e As EventArgs) Handles Button11.Click
Dim ThreadResync As System.Threading.Thread
ThreadResync = New System.Threading.Thread(AddressOf Bodacc_ResyncFTP)
ThreadResync.Start()
End Sub
Sub Bodacc_ResyncFTP()
Dim MyBodacc As bodacc_data = New bodacc_data
MyBodacc.Label_Status = Form1.Label1
MyBodacc.ResyncFTP()
End Sub
A way to update the UI with threading is the Delegate thingy, so in the bodacc_data I had to
Public Class bodacc_data
......
Delegate Sub UpdateLabelDelg(text As String, ThisLabel As Label)
Public Delegate_label As UpdateLabelDelg = New UpdateLabelDelg(AddressOf set_label)
Public Label_Status = Label
......
Sub set_label(stext As String, ThisLabel As Label)
ThisLabel.Text = stext
End Sub
.....
Sub ResyncFTP()
//says hello
If Label_Status.InvokeRequired = True Then
Label_Status.Invoke(Delegate_label, New Object() {"Working...", Label_Status})
Else
Label_Status.Text = "Working..."
End If
//do stuff
End Sub
End Class
It works like a charm. But I have many class doing more or less the same (disk update, database update, FTP update) and having to copy/past all the delegate / external label declaration / mini sub / invoke sound silly.
So I created a class to handle those UI update / delegate in order to have a quick access
Public Class Form_UI
Delegate Sub UpdateLabelDelg(text As String, ThisLabel As Label)
Public Delegate_label As UpdateLabelDelg = New UpdateLabelDelg(AddressOf set_label)
Private Labels(2) As Label
Sub New()
Labels(0) = Form1.Label1
Labels(1) = Form1.Label2
Labels(2) = Form1.Label3
End Sub
Sub set_label(stext As String, ThisLabel As Label)
ThisLabel.Text = stext
End Sub
Public Sub ChangeLabel(ByVal LabelNum As Integer, nText As String)
LabelNum = LabelNum - 1
If Labels(LabelNum).InvokeRequired Then
Labels(LabelNum).Invoke(Delegate_label, New Object() {nText, Labels(LabelNum)})
Else
Labels(LabelNum).Text = nText
Labels(LabelNum).Update()
End If
End Sub
End Class
So, now in the revamped bodacc_data and all others processing class I have only :
Public Class bodacc_data
......
Private MyUI as Form_UI
.....
Sub New()
MyUI = New Form_UI()
End Sub
Sub ResyncFTP()
//says hello
MyUI.ChangeLabel(1, "Working...")
//do stuff
End Sub
End Class
Question Why is MyUI.ChangeLabel not updating when the ResyncFTP is called in a thread, but works if called in the main thread (As in the code sample below)
Private Sub Button11_Click(sender As Object, e As EventArgs) Handles Button11.Click
Dim MyBodacc As bodacc_data = New bodacc_data
MyBodacc.ResyncFTP()
End Sub
Note that there is no error thrown. The notable weirdness is that <Form_UI>.ChangeLabel() never goes the .Invoke route but the normal update route. I strongly suspect a scope issue or insight issue.
When you create a windows forms app you set up a UI thread that is meant to be the owner of all the UI. The UI thread contains the message pump that is used to update all of the UI.
But what you're doing in Button11_Click is creating a new thread that goes and calls Dim MyBodacc As bodacc_data = New bodacc_data which, in turn, calls MyUI = New Form_UI().
You are creating a form on a non-UI thread. There is no message pump and therefore the UI doesn't update.
I'm looking at a console app for vb.net. I'm trying to get a worker thread to raise an event to the main thread to display data on the screen (the word "HIT" everytime the worker thread completes a cycle). My code is below.
I'm not sure why but the main thread's Private Sub CounterClass_GivingUpdate() Handles _counter.AboutToDistributeNewupdate isn't executing.
Imports System.Threading
Module Module1
Private WithEvents _counter As CounterClass
Private trd As Thread
Sub Main()
While True
Dim s As String = Console.ReadLine()
Dim started As Boolean
Select Case s
Case "status"
WriteStatusToConsole("You typed status")
Case "startcounter"
If started = False Then
starttheThread()
started = True
WriteStatusToConsole("You Have Started The Timer")
Else
WriteStatusToConsole("YOU HAVE ALREADY STARTED THE TIMER!!!")
End If
End Select
End While
End Sub
Private Sub CounterClass_GivingUpdate() Handles _counter.AboutToDistributeNewupdate
WriteStatusToConsole("Hit")
End Sub
Private Sub starttheThread()
Dim c As New CounterClass
trd = New Thread(AddressOf c.startProcess)
trd.Start()
End Sub
Sub WriteStatusToConsole(ByVal stringToDisplay As String)
Console.WriteLine(stringToDisplay)
End Sub
End Module
Public Class CounterClass
Public Event AboutToDistributeNewupdate()
Public Sub sendStatusUpdateEvent(ByVal updatestatus As String)
RaiseEvent AboutToDistributeNewupdate()
End Sub
Public Sub startProcess()
Dim i As Int64
Do
Thread.Sleep(1000)
i = i + 1
sendStatusUpdateEvent(i.ToString)
Loop
End Sub
End Class
Your CounterClass_GivingUpdate() only handles the _counter variable's event (the variable that you do not use!). Every time you declare a new CounterClass it has its own instance of the event that it raises.
You know have two options:
Option 1
Subscribe to the event for each new CounterClass instance you create. Meaning you must use the AddHandler statement every time you create a new instance of your class:
Private Sub starttheThread()
Dim c As New CounterClass
AddHandler c.AboutToDistributeNewupdate, AddressOf CounterClass_GivingUpdate
trd = New Thread(AddressOf c.startProcess)
trd.Start()
End Sub
Option 2
Mark the event as Shared to make it available without needing to create an instance of the class. For this you must also change how you subscribe to the event, by subscribing to it in your method Main():
Sub Main()
AddHandler CounterClass.AboutToDistributeNewupdate, AddressOf CounterClass_GivingUpdate
...the rest of your code...
End Sub
Private Sub CounterClass_GivingUpdate() 'No "Handles"-statement here.
WriteStatusToConsole("Hit")
End Sub
Public Class CounterClass
Public Shared Event AboutToDistributeNewupdate() 'Added the "Shared" keyword.
...the rest of your code...
End Class
I haven't really done much with threads before and I'm having a problem updating a label and closing a form.
When debugging, the CloseDialog() sub is definitely running in the main thread so I don't understand why it's not closing. There are no loops or anything else running on the form to keep it open. I'm also having a problem updating the text on a label with information passed from another thread in real time.
The AddOUToTreeView sub gets invoked and works fine, but the subs from frmStatus never do anything.
frmMain:
Private WithEvents bkg As New ADSearcher
Private Sub startSearch_Click(ByVal sender As Object, ByVal e As EventArgs) Handles startSearch.Click
With bkg
.RootPath = "LDAP://domain.com"
.FilterString = "(objectCategory=organizationalUnit)"
If Not integratedAuth Then
.UserID = "user"
.Password = "pass"
End If
.PageSize = 5
.PropertiesToLoad = New String() {"cn", "name", "distinguishedName", "objectCategory"}
Dim search As New Threading.Thread(AddressOf .StartSearch)
search.Start()
Dim statusDialog As frmStatus = New frmStatus
statusDialog.Show() 'I want to use ShowDialog() but removed it when trouble shooting
End With
End Sub
Private Delegate Sub displayStatus(ByVal entriesFound As Integer)
Private Sub bkg_ResultFound(ByVal ousFound As Integer) Handles bkg.ResultFound
Dim display As New displayStatus(AddressOf frmStatus.UpdateOUSearcherStatus)
Me.Invoke(display, New Object() {ousFound})
End Sub
Private Delegate Sub displayResult(ByVal node As TreeNode)
Private Delegate Sub closeStatusDialog()
Private Sub bkg_SearchCompleted(ByVal ouNodes As TreeNode) Handles bkg.SearchCompleted
Dim display As New displayResult(AddressOf AddOUToTreeView)
Me.Invoke(display, New Object() {ouNodes})
Dim closeStatus As New closeStatusDialog(AddressOf frmStatus.CloseDialog)
Me.Invoke(closeStatus)
End Sub
Private Sub AddOUToTreeView(ByVal node As TreeNode)
tvOU.Nodes.Add(node)
tvOU.TopNode.Expand()
End Sub
frmStatus (Both of these functions do nothing):
Public Sub CloseDialog()
'Me.DialogResult = Windows.Forms.DialogResult.OK
Me.Close()
'Me.Dispose()
End Sub
Public Sub UpdateOUSearcherStatus(ByVal entriesFound As Integer)
'lblOUsFound.Text = Format("{0} Organizational Units Found", ousFound)
lblOUsFound.Text = entriesFound.ToString
End Sub
In my ADSearcher class I have:
Public Event ResultFound(ByVal ousFound As Integer)
Public Event SearchCompleted(ByVal ouNodes As TreeNode)
and raise the events with:
RaiseEvent ResultFound(resultCount)
'and
RaiseEvent SearchCompleted(rootNode)