Edit: my orginal question was too confusing.
Public Class PrintResults
Public Sub ResultsToPS(ByVal lis As List(Of FileData), ByVal PSPathName As String)
Me.List = lis
'setup report writer
Dim rep As New Helper.Report
'create report to PS
rep.ToPS(PSPathName)
End Sub
Public Class Report
Public Sub New()
PrintDoc = New Printing.PrintDocument
End Sub
Public WithEvents PrintDoc As Printing.PrintDocument
Public PrintDocPrintPage As PrintDoc_PrintPage
Public Delegate Sub PrintDoc_PrintPage(ByVal sender As Object, _
ByVal e As Printing.PrintPageEventArgs) 'Handles PrintDoc.PrintPage
Public Function ToPS(ByVal PSPathName As String) As String
'fails
AddHandler Me.PrintDoc.PrintPage, AddressOf Me.PrintDocPrintPage
Me.PrintDoc.Print()
End Function
AddHandler has compiler error: AddressOf operand must be the name of a method. Is there any way to assign the handler to a delegate sub?
Maybe creating events and attaching a function to it is what you are looking for.
Sub Main()
Dim c As New TestClass
AddHandler c.OnComplete, AddressOf Test
c.Execute() ' Will print Done
Console.ReadLine()
End Sub
Sub Test()
Console.WriteLine("Done")
End Sub
Class TestClass
Public Event OnComplete()
Public Sub Execute()
RaiseEvent OnComplete()
End Sub
End Class
With some research I found it's not possible to use a delegate sub with AddressOf. So we need a different approach. I see it now that all I have to do is instantiate the PrintDoc in the New sub. Now I have a PrintDoc object for the AddHandler to use.
Public Class Report
Public Sub New()
PrintDoc = New Printing.PrintDocument
End Sub
We use the regular AddHandler with no delegates:
Public Class PrintResults
Public Sub ResultsToPS(ByVal lis As List(Of FileData), ByVal PSPathName As String)
Me.List = lis
'setup report writer
Dim rep As New Helper.Report
'PrintDoc is automatically instatiated so no object error
AddHandler rep.PrintDoc.PrintPage, AddressOf Me.PrintDoc_PrintPage
'create report to PS
rep.ToPS(PSPathName)
End Sub
Delegates and the AddressOf Operator
AddHandler Statement
Related
I have created a custom class
Public Class MyFSW
Inherits FileSystemWatcher
Public Property ParentForm As Form
Public Property TabPage As TabPage
End Class
Now I want to add a custom event to the this class, that fires when the property "EnableRaisingEvents" of the FileSystemWatcher changes?
Is there any chance to do this?
The EnableRaisingEvents property is not declared Overridable so you can't override it. You can shadow it though:
Public Class FileSystemWatcherEx
Inherits FileSystemWatcher
Public Property ParentForm As Form
Public Property TabPage As TabPage
Public Shadows Property EnableRaisingEvents As Boolean
Get
Return MyBase.EnableRaisingEvents
End Get
Set(value As Boolean)
If MyBase.EnableRaisingEvents <> value Then
MyBase.EnableRaisingEvents = value
OnEnableRaisingEventsChanged(EventArgs.Empty)
End If
End Set
End Property
Public Event EnableRaisingEventsChanged As EventHandler
Protected Overridable Sub OnEnableRaisingEventsChanged(e As EventArgs)
RaiseEvent EnableRaisingEventsChanged(Me, e)
End Sub
End Class
That will work as long as you set the property through a reference of type FileSystemWatcherEx. Because the property is shadowed rather than overridden, setting it through a reference of type FileSystemWatcher will bypass the derived property implementation and the event will not be raised. You really can't do anything about that.
Thank you so far!
This is what I got now (in my class-file):
Public Class MyFSW
Inherits FileSystemWatcher
Public Property ParentForm As Form
Public Property TabPage As TabPage
Public Shadows Property EnableRaisingEvents As Boolean
Get
Return MyBase.EnableRaisingEvents
End Get
Set(value As Boolean)
If MyBase.EnableRaisingEvents <> value Then
MyBase.EnableRaisingEvents = value
OnEnableRaisingEventsChanged(EventArgs.Empty)
End If
End Set
End Property
Public Event EnableRaisingEventsChanged As EventHandler
Protected Overridable Sub OnEnableRaisingEventsChanged(e As EventArgs)
RaiseEvent EnableRaisingEventsChanged(Me, e)
End Sub
End Class
In my Form-Class is got this:
Sub CreateFSW()
w.Watcher = New MyFSW With {
.ParentForm = f,
.TabPage = tp,
.Path = w.Path,
.Filter = w.Filter,
.IncludeSubdirectories = w.IncludeSubdirs,
.NotifyFilter = DirectCast(w.NotifyFilter, NotifyFilters)
}
AddHandler w.Watcher.Created, AddressOf Fsw_EventRaise
AddHandler w.Watcher.Changed, AddressOf Fsw_EventRaise
AddHandler w.Watcher.Renamed, AddressOf Fsw_EventRaise
AddHandler w.Watcher.Deleted, AddressOf Fsw_EventRaise
AddHandler w.Watcher.EnableRaisingEventsChanged, AddressOf Fsw_Event
End Sub
Private Sub Fsw_Event(sender As Object, e As FileSystemEventArgs)
MessageBox.Show("Works")
End Sub
The compiler says:
Option Strict On does not allow narrowing in implicit type conversions between method Fsw_Event(sender As Object, e As FileSystemEventArgs) and delegate Delegate Sub EventHandler(sender As Object, e As EventArgs)
Seems that some types does not match. I tried to change FileSystemEventArgs to EventArgs and stuff like this, but without luck.
As a continuation of a “previous example” you can achieve that with some small changes like the example below shows:
In this example the property is changed by clicking a button, but you can implement handlers on your own needs.
Option Strict On
Imports System.IO
Public Class Form1
Private Fsw As CustomFSW
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CreateFSW()
End Sub
Sub CreateFSW()
Fsw = New CustomFSW With {
.Name = "Tab1", 'Adding this you can specify the object is sending the event
.Path = "C:\Users\UserName\Desktop\Test\",
.Filter = "*.*",
.IncludeSubdirectories = True,
.EnableRaisingEvents = True
}
AddHandler Fsw.Created, AddressOf Fsw_Event
AddHandler Fsw.Changed, AddressOf Fsw_Event
AddHandler Fsw.Renamed, AddressOf Fsw_Event
AddHandler Fsw.Deleted, AddressOf Fsw_Event
'Here the handler of your custom event
AddHandler Fsw.MyCutomEvent, AddressOf Fsw_CutomEvent
End Sub
Private Class CustomFSW
Inherits FileSystemWatcher
Private counter As Integer = 0
Public Property Name As String
'You can use the base property instead of this for specific needs
Private _EnableRaisingEvents As Boolean
Public Overloads Property EnableRaisingEvents As Boolean
Get
Return _EnableRaisingEvents
End Get
Set(value As Boolean)
If Not value = _EnableRaisingEvents Then
counter += 1
RaiseEvent MyCutomEvent(Me, "Ciaooo, EnableRaisingEvents is changed " & counter.ToString & " times")
End If
_EnableRaisingEvents = value
End Set
End Property
'Rename this on your needs
Public Event MyCustomEvent(sender As Object, e As String)
End Class
Private Sub Fsw_CustomEvent(sender As Object, e As String)
' Do your stuff here
MsgBox(e)
End Sub
Private Sub Fsw_Event(sender As Object, e As FileSystemEventArgs)
Dim FSW As CustomFSW = CType(sender, CustomFSW)
If Not String.IsNullOrEmpty(FSW.Name) Then
Select Case FSW.Name
Case "Tab1"
'Do something
Debug.WriteLine("Event generated from: " & FSW.Name)
Case "Tab2"
'Do something else
Debug.WriteLine("Event generated from: " & FSW.Name)
End Select
End If
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If Fsw IsNot Nothing Then
Fsw.EnableRaisingEvents = Not Fsw.EnableRaisingEvents
End If
End Sub
End Class
I read various posts, and made a practice project, but it does not works.
The form have a button and a text box with a default text 'Updated 0 times'. On button click starts the timer and each time update the text with the number of times the text was updated.
The exception of cross thread calls is not thrown, but when calling the text box, its .Text = "", the text is updated but not the text box on the form. And InvokeRequired is always false.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'Here the textBox.Text = "Updated 0 times."
Dim checking_text As String = Me.TextBox1.Text
TimerTest.StartTimer()
End Sub
Delegate Sub UpdateTextInvoke(ByVal new_text As String)
Public Sub UpdateText(ByVal new_text As String)
'Here the textBox.Text = ""
Dim txtB As TextBox = Me.TextBox1
'InvokeRequired always = False.
If txtB.InvokeRequired Then
Dim invk As New UpdateTextInvoke(AddressOf UpdateText)
txtB.Invoke(invk, New Object() {new_text})
Else
'The value of this text box is updated, but the text on the form TextBox1 never changes
txtB.Text = new_text
End If
End Sub
End Class
Public Class TimerTest
Private Shared tmr As New System.Timers.Timer
Private Shared counter As Integer
Public Shared Sub StartTimer()
tmr.Interval = 5000
AddHandler tmr.Elapsed, AddressOf UdpateText
tmr.Enabled = True
End Sub
Public Shared Sub UdpateText(ByVal sender As Object, ByVal e As System.EventArgs)
counter += 1
Form1.UpdateText(String.Format("Updated {0} time(s).", counter))
End Sub
End Class
SOLVED
In the Class TimerTest added this code 'Private Shared myform As Form1 = Form1'
then changed 'Form1.UpdateText' To 'myform.UpdateText'
As indicated in the comments, you are using the default form instance feature of VB.Net. You could pass an instance of the form to the TimerTest class, and replace the reference to Form1 with the instance.
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim checking_text As String = Me.TextBox1.Text
TimerTest.StartTimer(Me)
End Sub
Public Sub UpdateText(new_text As String)
If TextBox1.InvokeRequired Then
Dim invk As New Action(Of String)(AddressOf UpdateText)
TextBox1.Invoke(invk, {new_text})
Else
TextBox1.Text = new_text
End If
End Sub
End Class
Public Class TimerTest
Private Shared tmr As New System.Timers.Timer()
Private Shared counter As Integer
Private Shared instance As Form1
Public Shared Sub StartTimer(formInstance As Form1)
instance = formInstance
tmr.Interval = 5000
AddHandler tmr.Elapsed, AddressOf UdpateText
tmr.Enabled = True
End Sub
Public Shared Sub UdpateText(ByVal sender As Object, ByVal e As System.EventArgs)
counter += 1
instance.UpdateText(String.Format("Updated {0} time(s).", counter))
End Sub
End Class
I’m trying to use events to update a textbox from a background worker from a different class.
It is the same problem as mentioned in this SO post, except I'm using VB.NET. I'm trying to implement the 2nd suggested solution by #sa_ddam213.
I’m getting an error: “Cross-thread operation not valid: Control 'txtResult' accessed from a thread other than the thread it was created on.”
Here is my code:
Public Class DataProcessor
Public Delegate Sub ProgressUpdate(ByVal value As String)
Public Event OnProgressUpdate As ProgressUpdate
Private Sub PushStatusToUser(ByVal status As String)
RaiseEvent OnProgressUpdate(status)
End Sub
Public Sub ProcessAllFiles(ByVal userData As UserData)
'Do the work
End Sub
End Class
Public Class MainForm
Private bw As New BackgroundWorker
Private dp As New DataProcessor
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
If bw.CancellationPending = True Then
e.Cancel = True
Else
dp.ProcessAllFiles(CType(e.Argument, UserData))
End If
End Sub
Private Sub dp_OnProgressUpdate(ByVal status As String)
txtResult.Text = status
End Sub
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
AddHandler dp.OnProgressUpdate, AddressOf dp_OnProgressUpdate
End Sub
End Class
Thanks all!
The event still comes from a different thread than the UI. You need to delegate the woke back to the UI. I don't check InvokeRequired because I know it is from the worker thread.
Me is the form, Invoke asks for the delegate that will handle the work of bring the data to the UI thread. Here my Delegate Sub is a lambda Sub instead of using a normal Sub routine - simpler design.
Private Sub dp_OnProgressUpdate(ByVal status As String)
'invoke the UI thread to access the control
'this is a lambda sub
Me.Invoke(Sub
'safe to access the form or controls in here
txtResult.Text = status
End Sub)
End Sub
Maybe you could try doing something like this?
Private Delegate Sub del_Update(ByVal status As String)
Private Sub dp_OnProgressUpdate(ByVal status As String)
If txtResult.InvokeRequired Then
txtResult.Invoke(New del_Update(AddressOf dp_OnProgressUpdate), New Object() {status})
Else
target_textbox.text = status
End If
End Sub
I'm passing values to and from a Background worker. To do this I'm using the following class to store the variables:
Class MyParameters
Friend _QueryStr As String
Friend _Value_CreatedDate As Object
Friend _AccountID As Object
Friend _Last_Login As Object
Friend _Motto As Object
Friend _Bio As Object
Friend _pFavourite_Game As Object
Friend _pFacebookLink As Object
Friend _pInstagram As Object
Friend _pTwitterLink As Object
Friend _pTumblerLink As Object
Friend _pYoutubeLink As Object
Friend _lblpYoutubeLink As String
Friend _lblpTumblerLink As String
Friend _lblpTwitterLink As String
Friend _lblpInstagram As String
Friend _lblFacebookLink As String
Friend _lblpFavourite_Game As String
Friend _lblBio As String
Friend _lblMotto As String
Friend _lblLast_Login As String
Friend _AccessCode As String
Friend _TargetUGPoints As String
Friend _Stream As New MemoryStream()
Friend _Image As Byte()
Friend _DoB_Value As Object
Friend _DoB As Date
Friend _Years As Integer
Friend _Months As Integer
Friend _Cautions_Value As Integer
Friend _Cautions As Integer
Friend _Bitmap As New Bitmap(_Stream)
Friend _Con As New SqlConnection("Data Source =" & My.Settings.ServerIP & ";Initial Catalog=Atlas;Integrated Security=False;User=" & My.Settings.UserName & ";Password=leaders132;")
Friend _MyCMD As New SqlCommand(_QueryStr, _Con)
End Class
And I'm calling the background worker like this on a forms load event:
Private Sub cdb_ProfileViewer_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Loadinglbl.Visible = True
SpinningProgress1.Visible = True
MsgBox("Starting MyParamters")
Dim m As New MyParameters
MsgBox("My Parameters set to m")
bw.RunWorkerAsync(m)
End Sub
However, the application doesn't seem to reach the background worker. There's no freezing or errors, it simply does nothing. I remain in control of the form like the background worker is working, but I dont think it's even starting because it doesn't run any msgbox's. For example, I've included a Msgbox in my code to let me know which stage the code has reached. It works if I place it before Dim m as New Parameter, but not after.
My Background worker, note I've omitted my code as it's lenthy:
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim m As MyParameters = DirectCast(e.Argument, MyParameters) 'Convert the generic Object back into a MyParameters object
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
e.Result = m
'Do some time-consuming work on this thread
end sub
My WorkerCompleted Event, also without code:
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Dim m As MyParameters = DirectCast(e.Result, MyParameters) 'Convert the generic Object back into a MyParameters object
MsgBox("Reached Worker Complete")
'Called when the BackgroundWorker is completed.
end sub
My BW Public Class Code:
Private bw As BackgroundWorker = New BackgroundWorker
Private Delegate Sub ButtonClick(ByVal sender As Object, ByVal e As EventArgs)
And the Public Sub responsible for the BW:
Public Sub New()
InitializeComponent()
bw.WorkerReportsProgress = False
bw.WorkerSupportsCancellation = False
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
End Sub
Is there anything that's jumping out at you? As I say, I feel it's something in my Class MyParamters.
The Load() event unfortunately swallows your exception. Move that code to the Constructor or the Shown() event and the problem becomes apparent:
Friend _Bitmap As New Bitmap(_Stream)
You can't create a Bitmap from an Empty Stream...
Minimum code to reproduce:
Public Class MyParameters
Friend _Stream As New MemoryStream()
Friend _Bitmap As New Bitmap(_Stream)
End Class
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim M As New MyParameters
End Sub
I am doing some transition coding, using the .net transitions library found # http://code.google.com/p/dot-net-transitions/. I am trying to add an event to fire on transitions completed. In my sub, I have the following statements:
Private Sub btnLogin_Click_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLogin.Click
If md5Password = rtnPassHash Then
AddHandler Me.TransitionCompletedEvent, AddressOf theHandlerFunction
Dim tr_empID = New Transition(New TransitionType_Linear(500))
tr_empid.add(txtEmployeeID, "BackColor", Color.LightGreen)
Dim tr_passw = New Transition(New TransitionType_Linear(500))
tr_passw.add(txtPassword, "BackColor", Color.LightGreen)
tr_empID.run()
tr_passw.run()
AddHandler Me.TransitionCompletedEvent, AddressOf theHandlerFunction
Dim tr_empID = New Transition(New TransitionType_Linear(500))
tr_empid.add(txtEmployeeID, "BackColor", Color.LightGreen)
Dim tr_passw = New Transition(New TransitionType_Linear(500))
tr_passw.add(txtPassword, "BackColor", Color.LightGreen)
tr_empID.run()
tr_passw.run()
end if
end sub
Outside of that sub I have:
Public Event TransitionCompletedEvent As EventHandler(Of Transition.Args)
Private Sub theHandlerFunction(ByVal sender As Object, ByVal args As Transition.Args) Handles Me.TransitionCompletedEvent
MsgBox("Event Fired")
End Sub
However, the event is not firing after the transition has finished. Why would this be?
Basic design:
Public Class Transition
Public Event TransitionCompleted(args As Transition.Args)
Public Sub SomeSub()
RaiseEvent TransitionCompleted(New Transition.Args With {set some properties})
End Sub
...
End Class
Public Class Form1
Private transition1 As New Transition
Private Sub Login_Click(...) ...
...
Addhandler transition1.TransitionCompleted, AddressOf TransitionCompleted
End Sub
Private Sub TransitionCompleted(args As Transition.Args) ' no handles clause
MessageBox.Show("event fired")
End Sub
End Class