VB.NET Delegate doesn't work - vb.net

In my application, I have a MainWindow with a ToolStripProgressBar and a ToolStripStatusLabel.
This properties:
Property ProgressBarPercantage() As Integer Implements BCSXPSearchTool.Presenter.IMainView.ProgressPercentage
Get
Return Me._progressbarpercentage
End Get
Set(ByVal value As Integer)
Me._progressbarpercentage = value
Me.StatusStripCurrentProgressBar.Value = Me._progressbarpercentage
End Set
End Property
Private _progressbarpercentage As Integer = 0
Property ProgressStatusText() As String Implements BCSXPSearchTool.Presenter.IMainView.ProgressStatusText
Get
Return Me._progressstatustext
End Get
Set(ByVal value As String)
Me._progressstatustext = value
Me.StatusStripCurrentState.Text = Me._progressstatustext
End Set
End Property
Private _progressstatustext As String = "Ready"
In the MainWindowPresenter I start a new BackgroundWorker which should read from a database.
Public Sub Search()
Dim bw As New BackgroundWorker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf runproc
If bw.IsBusy = False Then
bw.RunWorkerAsync()
End If
End Sub
Public Sub runproc()
Dim statusToSub As delegateStatusTo = AddressOf statusTo
Dim percToSub As delegatePercTo = AddressOf percTo
statusToSub.Invoke("Test")
'percToSub.Invoke(50)
End Sub
Public Sub percTo(ByVal value As Integer)
_view.ProgressPercentage = value
End Sub
Public Sub statusTo(ByVal value As String)
_view.ProgressStatusText = value
End Sub
Delegate Sub delegateStatusTo(ByVal value As String)
Delegate Sub delegatePercTo(ByVal value As Integer)
The code above is working. But if I change the sub runproc() to:
Public Sub runproc()
Dim statusToSub As delegateStatusTo = AddressOf statusTo
Dim percToSub As delegatePercTo = AddressOf percTo
' statusToSub.Invoke("Test")
percToSub.Invoke(50)
End Sub
It doesn't work. I get an exception:
InvalidOperationException
I got the text in english and can't translate it to english very well but I think something like:
The access to the control, created by another thread from another thread is not allowed.
I'm using Visual Studio 2008 Express + VB 2.0.
Thank you!

This is due to cross-thread UI access which is disallowed (but for every UI access, so your other code shouldn’t work either!). The easiest solution is to use BeginInvoke when required:
Public Sub statusTo(ByVal value As String)
If InvokeRequired Then
BeginInvoke(New Action(Of String)(AddressOf statusTo))
Return
End If
_view.ProgressStatusText = value
End Sub
Furthermore, #vulkanino’s comment is spot-on: your calls should be direct method calls, not delegate invocations.

Dim statusToSub As **new** delegateStatusTo(AddressOf WriteToDebug)
statusToSub.Invoke("Test")
Dim percToSub As **new** delegatePercTo (AddressOf percTo)
percToSub.Invoke(50)

It looks like you are attempting to access UI controls from the DoWork event handler. Remember, that event handler is running on a worker thread. You are not allowed to touch any UI control from a thread other than the one that created it. There is a ProgressChanged event that will be marshaled onto the UI thread automatically upon calling ReportProgress. You safely update the UI from this event.

Related

How to correctly use synclock to only allow one call to a function at a time

I have code that sends a ping to a batch of computers, and opens a VNC session to any that respond that they are online.
The pings are sent in a parallel.foreach loop, and an event is raised when a reply is receieved.
Despite putting a synclock block around the AddHost code, it is still being entered by multiple threads simultaneously.
I think it has something to do with the code being called by a raiseevent on another object.
This is the method that is handling the event
Private Sub AddHost(hostname As String)
' panesList is an list(of RemoteDesktop) prepopulated with 36 individual RemoteDesktop objects
Dim vnc As RemoteDesktop = panesList(Position)
vnc.GetPassword = New AuthenticateDelegate(Function() vncPassword)
SyncLock Semaphore
Try
If Position = 36 Then
Return
End If
vnc.Connect(hostname, 0, True, True)
Position += 1
Catch ex As Exception
End Try
End SyncLock
End Sub
Private Sub HandleConnect(hostFilter As String)
AddHandler OnlineFinder.LiveComputer, AddressOf AddHost
Dim bg As New Task(Sub() OnlineFinder.CheckFilteredASync(hostFilter))
bg.Start()
' AddHandler OnlineFinder.SearchComplete, AddressOf Finished
End Sub
From the object OnlineFinder.vb
Public Sub CheckFilteredASync(filterString As String)
Dim Ctable As DataTable = CTableAdapter.GetDataByPartName($"{filterString}%")
Dim clist As New List(Of String)
For Each drow As DataRow In Ctable.Rows
clist.Add(drow.Field(Of String)("Name"))
Next
Searching = True
Dim parallelLoopResult = Parallel.ForEach(clist, Sub(site) Ping(site))
RaiseEvent SearchComplete()
End Sub
Private Sub Ping(host As String)
If ValidPing(host) Then
RaiseEvent LiveComputer(host)
End If
End Sub
How should I be doing this so only one event is processed at a time (but it doesn't hang the UI)

Raise Event Vb.net from worker Thread

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

How to remove all event handlers from an event?

I have the following class
Public Class SimpleClass
Public Event SimpleEvent()
Public Sub SimpleMethod()
RaiseEvent SimpleEvent()
End Sub
End Class
I instanciate it like
Obj = New SimpleClass
AddHandler Obj.SimpleEvent, Sub()
MsgBox("Hi !")
End Sub
And i'm trying to remove the event handler dynamically-created using the code in : Code Project
(I assume a complex application where it's difficult to use : RemoveHandler Obj.Event, AddressOf Me.EventHandler)
In their code there is the following method
Private Shared Sub BuildEventFields(t As Type, lst As List(Of FieldInfo))
For Each ei As EventInfo In t.GetEvents(AllBindings)
Dim dt As Type = ei.DeclaringType
Dim fi As FieldInfo = dt.GetField(ei.Name, AllBindings)
If fi IsNot Nothing Then
lst.Add(fi)
End If
Next
End Sub
But when calling this code using my object type, the next line returns nothing
Dim fi As FieldInfo = dt.GetField(ei.Name, AllBindings)
means that somehow my event is not recognized as a field.
Does anyone know how to remove all event handlers from an event ?
Cheers in advance.
It is the lambda expression that is getting you into trouble here. Don't dig a deeper hole, just use AddressOf and a private method instead so you can trivially use the RemoveHandler statement.
If you absolutely have to then keep in mind that the VB.NET compiler auto-generates a backing store field for the event with the same name as the event with "Event" appended. Which makes this code work:
Dim Obj = New SimpleClass
AddHandler Obj.SimpleEvent, Sub()
MsgBox("Hi !")
End Sub
Dim fi = GetType(SimpleClass).GetField("SimpleEventEvent", BindingFlags.NonPublic Or BindingFlags.Instance)
fi.SetValue(Obj, Nothing)
Obj.SimpleMethod() '' No message box
I'll reiterate that you should not do this.

How to Pass Additional Parameters When Calling and Event VB.net

Public Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted, New
WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
Private Sub DoStuff(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
End Sub
How can I pass the homeTeam and guestTeam when firing the DocumentCompleted event.
I want to ge the above to values to inside the Dostuff method.
Please help.
First of all, you cannot have this hanging in the middle of nowhere:
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted,
New WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
AddHandler probably needs to be in some Initialize method, which could be inside Sub New, after InitializeComponent, or inside Form_Load, or as soon as you expect it to be triggered (after a specific event). Notice here that you are using a default event of a native .NET component, with a default event type. In this case you cannot directly consume anything other than what it already provides, when triggered. See WebBrowser.DocumentCompleted Event on MSDN.
You can, however, override all relevant classes and have your own MyWebBrowser control and your own event, with would contain additional properties. See below example:
Public Class Form1
Sub New()
' This call is required by the designer.
InitializeComponent()
Dim browser As New MyWebBrowser
AddHandler browser.MyDocumentCompleted, AddressOf DoStuff
End Sub
Private Sub DoStuff(ByVal sender As Object, ByVal e As MyWebBrowserDocumentCompletedArgs)
Dim guestTeam As String = e.GuestTeam 'guest team
Dim homeTeam As String = e.HomeTeam 'and home team are both accessible
'so you can do some processing on them
End Sub
Public Class MyWebBrowserDocumentCompletedArgs : Inherits WebBrowserDocumentCompletedEventArgs
Dim _homeTeam As String
Dim _guestTeam As String
Public ReadOnly Property HomeTeam
Get
Return _homeTeam
End Get
End Property
Public ReadOnly Property GuestTeam
Get
Return _guestTeam
End Get
End Property
Sub New(url As Uri, homeTeam As String, guestTeam As String)
MyBase.New(url)
_homeTeam = homeTeam
_guestTeam = guestTeam
End Sub
End Class
Public Class MyWebBrowser : Inherits WebBrowser
Public Delegate Sub MyWebBrowserDocumentCompletedEventHandler(e As MyWebBrowserDocumentCompletedArgs)
Public Event MyDocumentCompleted As MyWebBrowserDocumentCompletedEventHandler
Protected Overrides Sub OnDocumentCompleted(e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
MyBase.OnDocumentCompleted(e)
'homeTeam and guestTeam need to be extracted from the current instance of MyWebBrowser, and passed further
RaiseEvent MyDocumentCompleted(New MyWebBrowserDocumentCompletedArgs(e.Url, "homeTeam", "guestTeam"))
End Sub
End Class
End Class
If your project is relatively small, you can indeed have those as global variables, as #Vlad suggested in the comments.

Raising an event on a new thread in VB.NET

I need to raise an event from a form on a new thread.
(I don't believe the reason for this is relevant, but just in case: I'll be raising events from code within a form's WndProc sub. If the code handling the event blocks with something on a form [such as a msgbox], then all sorts of trouble occurs with disconnected contexts and what not. I've confirmed that raising events on new threads fixing the problem.)
This is what I am currently doing:
Public Event MyEvent()
Public Sub RaiseMyEvent()
RaiseEvent MyEvent
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Dim t As New Threading.Thread(AddressOf RaiseMyEvent)
t.Start()
End Sub
Is there a better way?
It is my understanding that events in VB are actually made up of delegates in the background. Is there any way to raise events in new threads without creating subs for each? Or, is there a more appropriate method I should be using?
You can eliminate the RaiseMyEvent sub like this:
Public Class Class1
Public Event MyEvent()
Sub Demo()
Dim t As New Threading.Thread(Sub() RaiseEvent MyEvent())
t.Start()
End Sub
End Class
Don't know if this will help, but i'll always do threading and events like this:
Event MyEvent(ByVal Var1 As String, ByVal Var2 As String)
Private Delegate Sub del_MyEvent(ByVal Var1 As String, ByVal Var2 As String)
Private Sub StartNewThread()
'MAIN UI THREAD
Dim sVar1 As String = "Test"
Dim sVar2 As String = "Second Var"
Dim oThread As New Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf StartNewThread_Threaded))
With oThread
.IsBackground = True
.Priority = Threading.ThreadPriority.BelowNormal
.Name = "StartNewThread_Threaded"
.Start(New Object() {sVar1, sVar2})
End With
End Sub
Private Sub StartNewThread_Threaded(ByVal o As Object)
'CHILD THREAD
Dim sVar1 As String = o(0)
Dim sVar2 As String = o(1)
'Do threaded operation
Threading.Thread.Sleep(1000)
'Raise event
RaiseEvent_MyEvent(sVar1, sVar2)
End Sub
Public Sub RaiseEvent_MyEvent(ByVal Var1 As String, ByVal Var2 As String)
If Me.InvokeRequired Then
'Makes the sub threadsafe (I.e. the event will only be raised in the UI Thread)
Dim oDel As New del_MyEvent(AddressOf RaiseEvent_MyEvent)
Me.Invoke(oDel, Var1, Var2)
Exit Sub
End If
'MAIN UI THREAD
RaiseEvent MyEvent(Var1, Var2)
End Sub