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)
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 have been very interested as of late in interfaces and the ability to further customize them beyond using them in their default state.
I have been researching IList(of T) specifically. The advantages of using generic lists as opposed to ArrayLists has astounded me. Here is a picture of a test. This is the site that goes into further explanation about the Test.
So, naturally I wanted to experiment. When I first iterate through the list with the ForNext method the code works fine. The second time I can't access the name of the Form in the list because it is disposed. Anyone have any insight how I can access the forms properties in the list.
Public Class frmMain
Dim Cabinet As List(Of Form) = New List(Of Form)
Dim FormA As New Form1
Dim FormB As New Form2
Dim FormC As New Form3
Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles _Me.Load
Cabinet.Add(FormA)
Cabinet.Add(FormB)
Cabinet.Add(FormC)
End Sub
Sub displayForm(ByVal aForm As Form)
Dim myFormName As String = ""
Stopwatch.Start()
If aForm.IsDisposed = False Then
aForm.Show()
Else
myFormName = aForm.(How do I access this objects Name?)
aForm = New Form '<----- I would rather simply use aForm = New(aForm)
aForm.Name = myFormName
aForm.Show()
End If
Stopwatch.Stop()
Dim RealResult As Decimal = (Stopwatch.ElapsedMilliseconds / 1000)
Debug.WriteLine(RealResult)
Stopwatch.Reset()
End Sub
Private Sub btnForEach_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnForEach.Click
'Dim instance as List
'Dim action as Action(of T)
'instance.ForEach(action)
'action = delegate to a method that performs an action on the object passeed to it
Cabinet.ForEach(AddressOf displayForm)
End Sub
I really don't understand why if VB knows that this is a Generic list, which means it is knowledgable of the list's type, and the objects are all constrained to be forms; why I can't call a constructor on an item in the list. Ex. aForm = New aForm or aForm = New Cabinet.aForm
Tear this one open for me somebody. Thanks.
You can't construct a new instance of "aForm" because its isn't a type, it is an instance of type Form.
If you wanted to prevent the ObjectDisposedException, you could hide the form instead of closing it. Place the following code in each forms code behind:
Public Class Form1
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
Dim form = CType(sender, Form)
form.Visible = False
e.Cancel = True
End Sub
End Class
This is a bit hacky, however, but then you wouldn't need the code in the Else block.
Edit
You could try this instead:
Private Sub displayForm(ByVal aForm As Form)
Dim indexOfCab As Integer = Cabinet.IndexOf(aForm)
If indexOfCab <> -1 Then
If aForm.IsDisposed Then
aForm = CreateForm(aForm.GetType())
Cabinet(indexOfCab) = aForm
End If
aForm.Show()
End If
End Sub
Private Shared Function CreateForm(formType As Type) As Form
Return CType(Activator.CreateInstance(formType), Form)
End Function
You wouldn't need that big Select statement.
This is the only way I have been able to get it to work. I feel it is extremely inefficient however, and hope someone can set me on a path to a better way to do this. The below is what I'm trying to achieve.
Sub displayForm(ByVal aForm As Form)
Dim myFormName As String = ""
If Cabinet.Contains(aForm) Then
Dim indexOfCab As Integer = Cabinet.IndexOf(aForm)
Dim ObjForm As Form = Cabinet.Item(indexOfCab)
If aForm.IsDisposed Then
Select Case indexOfCab
Case 0
aForm = Nothing
aForm = New Form1
Cabinet.Item(indexOfCab) = aForm
Cabinet.Item(indexOfCab).Show()
Case 1
aForm = Nothing
aForm = New Form2
Cabinet.Item(indexOfCab) = aForm
aForm.Show()
Case 2
aForm = Nothing
aForm = New Form3
Cabinet.Item(indexOfCab) = aForm
Cabinet.Item(indexOfCab).Show()
End Select
Else
Cabinet.Item(indexOfCab).Show()
End If
End If
End Sub
I have a feature whereby a class in a dll displays a form asking a user to clear a fault on a printer before clicking a button to say "Retry". The users have been just hitting retry without bothering to clear the fault so I am now coding an interlock:
The button on the invoked form is disabled until a call is made to an 'enable' method on the form.
This is done with delegate invocation as the events triggering these changes come from other dlls running on different threads.
The form's 'enable' method is wired into an EVENT HANDLER handling an event coming in from a different thread (one that monitors an ethernet IO Server).
The problem I have is that THE "_Fault_StateChanged" EVENT NEVER FIRES. I suspected the cause was the "ShowDialog" and "DialogResult" technique I have used here, but I have used this exact same technique elsewhere in this application.
Any suggestions would be great
See code extract below:
MAIN CLASS excerpt
Public Class StatePrintHandler
Private WithEvents _RetryForm As frmRetryReject
Private Delegate Sub delShowRetryDialog()
Private Delegate Sub delResetEnable()
Private Sub InvokeResetEnable()
Dim del As delResetEnable
del = New delResetEnable(AddressOf ResetEnable)
del.Invoke()
End Sub
Private Sub InvokeRetryDialogue()
Dim del As delShowRetryDialog
del = New delShowRetryDialog(AddressOf ShowRetryDialog)
del.Invoke()
End Sub
Private Sub ShowRetryDialog()
_RetryForm = New frmRetryReject
_RetryForm.Prep()
_RetryForm.ShowDialog()
If (_RetryForm.DialogResult = Windows.Forms.DialogResult.OK) Then
Me._RetryForm.Visible = False
End If
End Sub
Private Sub ResetEnable()
If (Not IsNothing(_RetryForm)) Then
_RetryForm.ResetEnable()
Else
AuditTrail("Retry form not active, no action", True)
End If
End Sub
'Event handler for status change coming in on a different thread
Private Sub _Fault_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _fault.StateChanged
If (e.NewState) Then
AuditTrail("Labeller has faulted out during cycling", True)
Else
InvokeResetEnable()
End If
End Sub
End Class
RETRY FORM CLASS excerpt
Public Class frmRetryReject
Private Delegate Sub delEnable()
Public Event Complete()
Public Sub Prep()
Me.OK_Button.Enabled = False
End Sub
Private Sub OK_Button_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OK_Button.Click
Me.DialogResult = System.Windows.Forms.DialogResult.OK
Me.Close()
End Sub
Public Sub ResetEnable()
If (IsHandleCreated) Then
Dim params() As Object = {}
Me.Invoke(New delEnable(AddressOf InvokeEnable), params)
End If
End Sub
Private Sub InvokeEnable()
Me.OK_Button.Enabled = True
End Sub
End Class
Additional detail in response to Daniel's comments
The code is incomplete here, it's an excerpt. The fault object is a subscription to an external library and is a handler for an ethernet IO server.
The _fault StateChanged event fires when a digital input on an IOServer changes. I know the following:
My trace files show the signal change high, this invokes the retry form.
The signal physically changes low again when the retry screen is still showing.
...but the event does not fire
It's as if the application cannot service the event coming in until the ShowDialog/DialogResult completes - but I am confused about this because I understood that the ShowDialog in .NET 2.0 did not block, I should still be able to service events, and have used this same pattern elsewhere in the app.
A couple of things to note:
The MAIN CLASS is instantiated dynamically at runtime via reflection based on configuration.
This is VS2005 SP2
I will post the entire class in another code box if this helps, but it may crowd the scene...
Thanks
Andy
Imports System.IO
Imports ACS.Interfaces
Imports ACS.Pallet
Public Class StateCimPAKPrintHandler
Inherits StateBase
Private WithEvents _ioManager As ACS.Components.DigitalIOManager
Private _config As StateCimPAKPrintHandlerBootstrap
Private CONST_OutcomeOK As String = "OK"
Private CONST_OutcomeRetry As String = "Retry"
Private CONST_OutcomeException As String = "Exception"
Private WithEvents _busy As ACS.Drivers.Common.InputSignal
Private WithEvents _fault As ACS.Drivers.Common.InputSignal
Private WithEvents _print As ACS.Drivers.Common.OutputSignal
Private WithEvents _palletRelease As ACS.Drivers.Common.OutputSignal
Private _labellingInProgress As Boolean
Private _faulted As Boolean
Private _retryScreenInvoked As Boolean
Private WithEvents _timeout As System.Timers.Timer
Private WithEvents _faultTimer As System.Timers.Timer
Private WithEvents _RetryForm As frmRetryReject
Private Delegate Sub delShowRetryDialog()
Private Delegate Sub delResetEnable()
Private Sub InvokeResetEnable()
Dim del As delResetEnable
del = New delResetEnable(AddressOf ResetEnable)
del.Invoke()
End Sub
Private Sub InvokeRetryDialogue()
Dim del As delShowRetryDialog
del = New delShowRetryDialog(AddressOf ShowRetryDialog)
del.Invoke()
End Sub
Private Sub ShowRetryDialog()
_timeout.Stop()
_retryScreenInvoked = True
_RetryForm = New frmRetryReject
_RetryForm.Prep()
AuditTrail("Displaying Retry screen", True)
_RetryForm.ShowDialog()
If (_RetryForm.DialogResult = Windows.Forms.DialogResult.OK) Then
AuditTrail("User clicked RETRY LINE on the RETRY dialogue", True)
_retryScreenInvoked = False
Me._RetryForm.Visible = False
Me.SetOutcome(CONST_OutcomeRetry)
End If
End Sub
Private Sub ResetEnable()
If (Not IsNothing(_RetryForm)) Then
_RetryForm.ResetEnable()
Else
AuditTrail("Retry form not active, no action", True)
End If
End Sub
Public Sub New(ByVal Sequencer As ISequencer, ByVal ParentPlt As ACS.Interfaces.IPallet, ByVal Name As String)
MyBase.New(Sequencer, ParentPlt, Name)
_timeout = New System.Timers.Timer
_faultTimer = New System.Timers.Timer
_config = New StateCimPAKPrintHandlerBootstrap(Me._myIniFileName, Name)
_timeout.Interval = _config.CycleTimeoutMS
_faultTimer.Interval = 750
_retryScreenInvoked = False
_RetryForm = New frmRetryReject
Me._RetryForm.Visible = False
_ioManager = ACS.Components.DigitalIOManager.GetInstance
_busy = _ioManager.GetInput("Busy")
_fault = _ioManager.GetInput("CimPAKFault")
_print = _ioManager.GetOutput("Print")
_palletRelease = _ioManager.GetOutput("PalletRelease")
End Sub
Public Overrides Sub Kill()
_ioManager = Nothing
_RetryForm = Nothing
_busy = Nothing
_fault = Nothing
_print = Nothing
_timeout = Nothing
_faultTimer = Nothing
_pallet = Nothing
End Sub
Public Overrides Sub Execute()
AuditTrail("Pulsing Print Signal", True)
_print.PulseOutput(3000)
_labellingInProgress = True
_timeout.Start()
End Sub
Private Sub _busy_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _busy.StateChanged
_timeout.Stop()
AuditTrail("Busy signal changed to : " & e.NewState, True)
If (e.NewState) Then
_faulted = False
AuditTrail("CimPAK = Busy High", True)
_labellingInProgress = True
Else
AuditTrail("CimPAK = Busy Low", True)
AuditTrail("Wait 750 milliseconds for any faults", True)
_faultTimer.Start()
End If
End Sub
Private Sub _Fault_StateChanged(ByVal sender As Object, ByVal e As Drivers.Common.DigitalSignalChangedEventArgs) Handles _fault.StateChanged
AuditTrail("Fault signal changed to : " & e.NewState, True)
If (e.NewState) Then
If (_labellingInProgress = True) Then
AuditTrail("Labeller has faulted out during cycling", True)
_faulted = True
If (Not _retryScreenInvoked) Then
InvokeRetryDialogue()
End If
Else
AuditTrail("Labeller has faulted out between cycles, no action can be taken", True)
End If
Else
If (_retryScreenInvoked) Then
AuditTrail("Enable button on Retry screen", True)
InvokeResetEnable()
End If
_faulted = False
End If
End Sub
Private Sub _faultTimer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _faultTimer.Elapsed
_faultTimer.Stop()
If (_faulted) Then
AuditTrail("System has faulted", True)
Else
AuditTrail("No fault occured, assume pallet is OK to release", True)
AuditTrail("CimPAK cycle complete", True)
_labellingInProgress = False
_palletRelease.PulseOutput(3000)
Me.SetOutcome(CONST_OutcomeOK)
End If
End Sub
Private Sub _timeout_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles _timeout.Elapsed
_timeout.Stop()
AuditTrail("Labeller print cycle timed out", True)
If (Not _retryScreenInvoked) Then
_retryScreenInvoked = True
InvokeRetryDialogue()
InvokeResetEnable()
End If
End Sub
End Class
#Region "Bootstrap"
Public Class StateCimPAKPrintHandlerBootstrap
Private Const CONST_CycleTimeoutMS As String = "CycleTimeoutMS"
Private _CycleTimeoutMS As Long
#Region "Properties"
Public ReadOnly Property CycleTimeoutMS() As Long
Get
Return _CycleTimeoutMS
End Get
End Property
#End Region
Public Sub New(ByVal IniFile As String, ByVal Name As String)
Try
Dim _cfgFile As String = Environ("ACSVAR") & "\" & IniFile
' Check to see if the CFG file exits
If File.Exists(_cfgFile) = False Then
Throw New Exception("Configuration file does not exist: " & _cfgFile)
Else
'Get values
_CycleTimeoutMS = ACS.Utility.Configuration.GetLong(_cfgFile, Name, CONST_CycleTimeoutMS)
End If
Catch ex As Exception
Throw
End Try
End Sub
End Class
#End Region
Try to look at
Visual Basic, Child Thread Blocking Main Thread
answer for in Multithreading example and
New Thread is still blocking UI-Thread
answer from ( me ) for In Model Threading.... Those 2 should give you writen code to start aswell.
It's been a long time since I did any Winforms work, but are you showing the dialog modally? Because that could be the cause of the delegate invokation not arriving.
Asynchronous delegates work by serializing the invokation details into a memory location internal to the framework and then posting a window message to the top-level window for the relevant thread. Modal dialogs work by going into a message processing loop that steals all the messages for the thread so that only that one dialog can respond. You can see how this might conflict.