How can I put AddressOf, from another class?
I get this error 'AddressOf' operand must be the name of a method (without parentheses). "
Is there an Eval () function in VB.NET? Or how does one do this?
Public Shared Property e As UserControl
Public Shared Sub SetButton(ByVal button As String, ByVal Objekt As [Delegate])
Dim errorbuttom1 As Button = e.FindName("errorButton1")
AddHandler errorbuttom1.Click, AddressOf Objekt
End Sub
You have to have an instance of Objekt, and the method that's the delegate must be public and match the signature of the delegate. Or, the method must be public static.
I believe that would work...
Related
I have a weird problem that I can't wrap my head around.
I have the following code:
Public Class Form1
Public WithEvents MyClass1 As New MyClass
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Private Sub MyClass_UpdateListbox() Handles MyClass1.UpdateListbox
For Each sItem as String In MyClass1.Listbox
MsgBox(sItem) 'an MsgBox shows correct items each time.
Next sItem
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
Me.Listbox.Items.Add("event triggered") 'does nothing.
End Sub
End Class
Public Class MyClass
Public Listbox as new Listbox
Public Event UpdateListbox()
Public Sub New()
'Constructor. sub.
Me.AddItem("Populating listbox")
End Sub
Public Sub AddItem(sItem as String)
Me.Listbox.Items.Add(sItem)
RaiseEvent UpdateListbox()
End Sub
End Class
If I comment the following lines in above code, the listbox keeps adding event triggered, as expected. Of course, I don't have to remove the clear one. It will work, but then it just adds the same item. If I use a command button and call MyClass.AddItem("Something") that is correctly added too as long as the below is commented out. But if not, then once the listbox is in broken state, nothing can be added anymore.
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
How can I use a virtual listbox and assign it to my real listbox?
Also, instead of assigning one listbox to the other, I can of course use that for each loop and add each item one by one which works, but that for each look was for debugging purpose in the first place.
EDIT:
My goal with this application is to build a Todo list with features that are not in a todolist. This is a project I build for work because there I need a tool like this. I already have a todolist that I use but I built it wrong in the past. Everything was condensed in form1, no modules no extra classes. As a result I got weird bugs that I patched with workarounds. I am now rebuilding the application from the ground up, separating tasks in its own classes so I can apply business logic and have a true OOP application. The todo list will become its own class, and managing the list etc will be handeled by this class. It interacts with controls on the form, such as buttons and listboxes. If I just use form1.listbox from the class, things break at program start. I started another question and the below code was a now deleted answer. At first I did not get it working because I did not realize the listbox crashes if I assign it the virtual instance.
So my goal is to have the todolist be handled entirely by the todolist class. It does need a way to interact with controls on form1, and that is the puzzle I'm currently trying to solve.
In the original code, the main problem is that the Field that hold the instance of a Control shown if a Form is reassigned to the instance of another ListBox Control defined in a custom class:
Me.Listbox = Me.MyClass1.Listbox
From now on, Me.Listbox points another ListBox that is not show on screen, so any attempt to update the Form's child ListBox fails, except when Me.Listbox.Items.Clear() is called - in the same procedure - after it's being reassigned, because the handle of the Owner of the ObjectCollection (the object that holds the Items shown in the ListBox) has not been updated yet. It's going to fail after the current method exits nonetheless.
As noted in comments, this is a simplified method to handle a Form and its child Controls using a handler class. The contract between the class handler and a Form is sealed by an Interface (named IFormHandler here).
A Form that implements this Interface exposes the methods defined by the Interface that allow to trigger Actions and specific behaviors, depending on the Type of Control and the implementation.
I suggest to take a look at the MVP or ReactiveUI (MVVM-derived) for WinForms Patterns.
How too proceed:
Open up the ApplicationEvents class object.
If you don't have it already, select Project -> Properties -> Application and click the View Application Events button. It will generate ApplicationEvents.vb. Find it in Solution Explorer and open it up.
It should look like this (plus a bunch of comments that explain what it's for):
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
End Class
End Namespace
Paste into MyApplication these lines of code:
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
Public SomeFormHandler As MyFormHandler(Of SomeForm)
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
SomeFormHandler = New MyFormHandler(Of SomeForm)
Return MyBase.OnStartup(e)
End Function
End Class
End Namespace
Add an Interface that defines the Actions (or Behaviors) that a Form must implement.
Here, the GetUsersList() method specifies that a Form that implements this Interface must return the instance of a child ListBox Control.
(To add an Interface, select Project -> Add -> New Item... and select the Interface template. Name the file IFormHandler)
Extend this Interface as needed, to add more Methods or Properties that define actions and behaviors.
Public Interface IFormHandler
Function GetUsersList() As ListBox
End Interface
A Form that implements the IFormHandler Interface implements and exposes the GetUsersList() method, which returns the instance of a ListBox Control (named usersList here)
There's nothing else to do with this Form, the control is handed over to the MyFormHandler object that is initialized with this Type.
Public Class SomeForm
Implements IFormHandler
Public Sub New()
InitializeComponent()
End Sub
Public Function GetUsersList() As ListBox Implements IFormHandler.GetUsersList
Return Me.usersList
End Function
End Class
Now, to show SomeForm, you can use the MyFormHandler class object show below.
' Set the Owner if called from another Form
My.Application.SomeFormHandler.Show(Me)
' Or without an Owner
My.Application.SomeFormHandler.Show()
To close SomeForm, you can either use its handler:
My.Application.SomeFormHandler.Close()
or close it as usual:
[SomeForm Instance].Close()
If MyFormHandler determines that the instance of SomeForm has been disposed, it creates a new one when you call its Show() method again later.
To update the ListBox Control of SomeForm, use the public methods exposed by the MyFormHandler class:
' Add a new element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.AddElement, "Some Item")
' Remove an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.RemoveElement, "Some Item")
' Replace an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.ReplaceElement, "New Item", "Some Item")
' Clears the ListBox
My.Application.SomeFormHandler.ClearUsersList()
All these actions generate an event that you can subscribe to when needed.
See also the example that shows how to raise a custom event when the ListBox raises one of its stardard events; SelectedIndexChanged is handled here.
See the implementation of MyFormHandler.
Generic Form handler:
A Form needs to implement the IFormHandler Interface for the MyFormHandler class to accept it as valid.
You can of course extend the Interface, to add more Actions, or build a MyFormHandler class object that uses a different Interface, or more than one.
Public Class MyFormHandler(Of TForm As {Form, IFormHandler, New})
Implements IDisposable
Private formObject As TForm
Private IsInstanceSelfClosing As Boolean = False
Public Event UsersListUpdate(item As Object, changeType As UpdateType)
Public Event UsersListIndexChanged(index As Integer)
Public Sub New()
InitializeInstance()
Dim lstBox = formObject.GetUsersList()
AddHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
End Sub
Private Sub InitializeInstance()
formObject = New TForm()
AddHandler formObject.FormClosing, AddressOf OnFormClosing
End Sub
Private Sub OnFormClosing(sender As Object, e As FormClosingEventArgs)
IsInstanceSelfClosing = True
Dispose()
End Sub
Public Sub UpdateUsersList(updateMode As UpdateType, newItem As Object, Optional oldItem As Object = Nothing)
If newItem Is Nothing Then Throw New ArgumentException("New Item is null")
Dim lstBox = formObject.GetUsersList()
Select Case updateMode
Case UpdateType.AddElement
lstBox.Items.Add(newItem)
Case UpdateType.RemoveElement
lstBox.Items.Remove(newItem)
Case UpdateType.ReplaceElement
If oldItem Is Nothing Then Throw New ArgumentException("Replacement Item is null")
Dim index = lstBox.Items.IndexOf(oldItem)
lstBox.Items.Remove(oldItem)
lstBox.Items.Insert(index, newItem)
Case Else : Return
End Select
RaiseEvent UsersListUpdate(newItem, updateMode)
End Sub
Public Sub ClearUsersList()
formObject.GetUsersList().Items.Clear()
End Sub
Private Sub OnUsersListIndexChanged(sender As Object, e As EventArgs)
RaiseEvent UsersListIndexChanged(DirectCast(sender, ListBox).SelectedIndex)
End Sub
Public Sub Show(Optional owner As IWin32Window = Nothing)
If formObject Is Nothing OrElse formObject.IsDisposed Then InitializeInstance()
If formObject.Visible Then
formObject.WindowState = FormWindowState.Normal
formObject.BringToFront()
Else
formObject.Show(owner)
End If
End Sub
Public Sub Close()
If formObject IsNot Nothing AndAlso (Not formObject.IsDisposed) Then
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
IsInstanceSelfClosing = False
Dispose()
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If disposing Then
If formObject Is Nothing OrElse formObject.IsDisposed Then Return
Dim lstBox = formObject.GetUsersList()
RemoveHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
If Not IsInstanceSelfClosing Then formObject.Close()
IsInstanceSelfClosing = False
End If
End Sub
End Class
Enumerator used in MyFormHandler:
Public Enum UpdateType
AddElement
RemoveElement
ReplaceElement
End Enum
I have a class 'oBnd' defined as Object that can be assigned as type clsBound or clsClaim.
Claim and bound are identical from the outside, same methods etc.
I call the various properties using 'CallByName'
ie Dim Current As String = CallByName(oBnd, PropName, CallType.Get)
When a property is changed in either class the DirtyStatus event is raised by that class.
I am having a problem attaching to this event.
If I try
AddHandler oBnd.DirtyStatus, AddressOf oBnd_DirtyStatus
I get the error "DirtyStatus is not an event of Object" I guess that makes sense as clearly object know nothing of my dirtystatus.
I tried using:
AddHandler DirectCast(oBnd, clsBound).DirtyStatus, AddressOf oBnd_DirtyStatus
While this does fix the error it does not get called when the DirtyStatus event is raised.
oBnd is defined as
Private WithEvents oBnd As Object
It is global to the form
oBnd gets set as
oBnd = New clsBound(mvarBUDConnection)
AddHandler oBnd.DirtyStatus, AddressOf oBnd_DirtyStatus
oBnd.Load(CInt(txtTrans.Text))
BuildPage(oBnd)
Or
oBnd = New clsClaim(mvarBUDConnection)
AddHandler oBnd.DirtyStatus, AddressOf oBnd_DirtyStatus
oBnd.Load(CInt(txtTrans.Text))
BuildPage(oBnd)
The oBnd_DirtyStatus sub, that I am trying to attach to, looks like this
Private Sub oBnd_DirtyStatus(IsDirty As Boolean) ' Handles oBnd.DirtyStatus
Me.Text = "QFix"
If IsDirty Then
Me.Text = "QFix - Pending Save"
btnSave.Enabled = True
Else
btnSave.Enabled = False
End If
End Sub
How can I attach a handle to this event?
Here is how you can both get Events working and get away from using Reflection to access properties. Even given the public methods are similar but the data being carried is very different it should still possible to use OOP/Inheritance.
Public Enum ClaimBoundType
None ' error!!!!
Claim
Bound
End Enum
Public MustInherit Class ClaimBase
' type tracker usually rather handy
Public Property ItemType As ClaimBoundType
Public Sub New(t As ClaimBoundType)
ItemType = t
End Sub
' low rent INotifyPropertyChanged
Public Event DataChanged(sender As Object, e As EventArgs)
' "universal" prop: works the same for all derived types
Private _name As String = ""
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
If value <> _name Then
_name = value
BaseDataChanged(Me)
End If
End Set
End Property
' props which must be implemented; 1 or 100 doesnt matter
MustOverride Property CurrentValue As Integer
' methods which must be implemented
MustOverride Function DoSomething() As Integer
' raise the changed event for base or derived classes
Protected Friend Sub BaseDataChanged(sender As Object)
RaiseEvent DataChanged(sender, New EventArgs())
End Sub
End Class
You'd have to do some basic data analysis to figure out which Properties and Methods can be implemented in the base class (as with Name above) and which in the inherited classes. There are usually at least some which can be done in the base class.
Your derived classes can implement the methods in totally different ways and load data from where ever:
Public Class Claim
Inherits ClaimBase ' the IDE will add all the MustInherits when
' you press enter
Public Sub New()
MyBase.New(ClaimBoundType.Claim)
End Sub
Public Overrides Function DoSomething() As Integer
' what happens here can be completely different
' class to class
End Function
Private _CurValue As Integer = 0
Public Overrides Property CurrentValue As Integer
Get
Return _CurValue
End Get
Set(Value As Integer)
If _CurValue <> Value Then
_CurValue = Value
OnDataChanged("CurrentValue")
End If
End Set
End Property
' name of prop that changed not actually used here, but
' is usually good to know (use custom args or INotifyPropertyChanged)
Public Sub OnDataChanged(pname As String)
' fire shared datachanged event
MyBase.BaseDataChanged(Me)
End Sub
End Class
How to Use Them
Now you can implement them without resorting to Object, subscribe to the event and not have to use Reflection to get/set properties:
' 'generic' object variable: DONT/CANT USE [New] w/ClaimBase
Private myCB As ClaimBase
...
' set it as a Claim instance...
' This is perfectly legal because Claim is also a ClaimBase Type:
myCB = New Claim
' hook up the event handler
AddHandler myCB.DataChanged, AddressOf cb_DataChanged
You can declare your object variables as ClaimBase, but you cannot create an instance of ClaimBase since it is abstract/MustInherit. Since the event is part of the base class, there is no problem with syntax. The form level handler:
' Use standard (sender, e) signature
' (CA will object to other signatures:)
Private Sub cb_DataChanged(sender As Object, e As EventArgs)
' do change stuff here
...
End Sub
Best of all, you can reference properties directly:
cbObj.Name = "Ziggy" ' will fire the event from the base class
cbObj.CurrentValue = 42 ' fires event from the Claim class
I added the ItemType property so you can tell them apart at run time (ie when you hold the mouse over a ClaimBase object variable). If/when there are Type specific properties/methods to access, cast it (from what you said, there cant be any of these now):
If cbObj.ItemType = ClaimBoundType.Claim Then
CType(cbObj, Claim).ClaimSomething = 5
End If
Also use ClaimBase as the declaration Type for Lists and method signatures also to allow either type to be passed rather than boxing them (converting to Object):
Private cbList As New List(Of ClaimBase)
...
' just an example of the declaration
Private Sub AddThingToList(cb As ClaimBase)
cbList.Add(cb)
End Sub
I did not go into INotifyProperty in order to focus on Inheritance, though the basics of it are in that base class. It is a more systemic way to implement the DataChanged/DirtyStatus event and detection.
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.
Please take a look at the code below, which works as I would expect:
Partial Class _Default
Inherits System.Web.UI.Page
Delegate Sub TestEventHandler(ByVal o As Object, ByVal e As EventArgs)
Dim alhandler As TestEventHandler = AddressOf TestEventMethod
Public Event Test1 As TestEventHandler
Public Event Test2 As TestEventHandler
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
AddHandler Me.Test1, alhandler
AddHandler Me.Test2, alhandler
RaiseEvent Test1(Me, e)
RaiseEvent Test2(Me, e)
RemoveHandler Me.Test1, alhandler
RaiseEvent Test1(Me, e)
End Sub
Public Sub TestEventMethod(ByVal o As Object, ByVal e As EventArgs)
MsgBox("Test")
End Sub
End Class
I am confused with the two statements below:
Dim alhandler As TestEventHandler = AddressOf TestEventMethod '1
Public Event Test1 As TestEventHandler '2
1) This is saying that the reference of alHandler is a delegate that
points to the address of a function. 2) This is saying that Test1 is
an event of type Delegate. How can an event and a handler be a
delegate?
As others have said, each type of delegate is a type, just as if it were a regular class. So, in your example code, TestEventHandler is a delegate type. A TestEventHandler variable can reference any TestEventHandler object, just like any other variable can reference any object of its own type. TestEventHandler objects must be instantiated, just like objects of any other type.
Delegates are special, however, in that you declare them using a different, special, syntax. For instance, if you had the following method:
Public Sub MyMethod(Text As String)
' ...
End Sub
You could create a delegate that matches that method signature like this:
Public Delegate Sub MyMethodDelegate(Text As String)
Remember, by doing so, that simply defines the MyMethodDelegate type. That doesn't declare a variable of that type nor does it instantiate an object of that type.
There are two things in VB.NET syntax, however, which often cause a lot of confusion. First, when you declare an event, you can use two different syntax:
Public Event MyEvent As MyMethodDelegate
Public Event MyEvent(Text As String)
Both of those lines do the same thing. The first line defines the event using an already defined delegate type. The second line essentially defines a new unnamed delegate on the fly and then uses it as the type for the event. (Note, I'm using the MyMethodDelegate for simplicity, and that will work, but standards dictate that events should have a sender and an event args.) When an event is declared, think of it like a variable. Behind the scenes, it's really like a collection object that keeps a list of all the delegate objects that are added to it using the AddHandler function.
The second confusing thing in VB.NET is that the compiler will automatically instantiate a new delegate object for you, if necessary, when you use the AddressOf function. So, for instance, when you do something like this:
AddHandler myObject.MyEvent, AddressOf MyMethod
It's just a shortcut for typing the full text, like this:
AddHandler myObject.MyEvent, New MyMethodDelegate(AddressOf MyMethod)
The latter, in my opinion is much more clear. What you are actually doing is creating a new object of that delegate type and pointing that delegate object to that method, and then adding that delegate object to the event (that event collection-like variable defined by the object's type).
So, in your example, this line:
Dim alhandler As TestEventHandler = AddressOf TestEventMethod
Would be more clearly written as:
Dim alhandler As TestEventHandler = New TestEventHandler(AddressOf TestEventMethod)
It's declaring a delegate variable and then setting it to a new delegate object that points to that particular method. In this case, it's just a standard delegate variable, not an event. Events are very similar to delegates field/properties. Events are essentially an accessor wrapper around a private delegate field, in the same way that properties often wrap a private field. The big differences between delegate fields and events are that events support the AddHandler and EventHandler functions and events cannot be raised/invoked from outside of the class that defines it.
I have a class in VB.NET that has a method (called CurrentValue) that returns a number. There is also an event that the class raises to indicate the number has changed. In the event handler on my form, I update a textbox using the exposed method.
Sort of like this:
Public WithEvents MyClass as New CustomClass
Private Sub MyClass_DataChanged() Handles MyClass.DataChanged
Text1.Text = MyClass.CurrentValue
End Sub
When I run this I get a "Debugger.Runtime.CrossThreadMessagingException" error. What could be doing this? I am instantiating MyClass in the same form that contains the textbox.
I can also set properties of the MyClass object without any trouble.
OK, here's what I did:
In the form I have this to handle the event:
Public Delegate Sub MyClassDataChangedDelegate()
Sub MyClassDataChanged() Handles MyClass.DataChanged
If Me.InvokeRequired Then
Me.Invoke(New MyClassDataChangedDelegate(AddressOf MyClassDataChanged))
Else
Me.Text1.Text = MyClass.CurrentValue
End If
End Sub
This seems to work. Thanks for the suggestion.