Please see the code below:
Imports ComponentAndControl
Public Class Form1
Delegate Function Compare1(ByVal intNumber1 As Integer, ByVal intNumber2 As Integer) As Boolean
Public Event e()
Public Event e2(ByVal o As Object, e As EventArgs)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim Test As New TestEventArgs
Test.Name = "Bert"
Test.Age = 34
RaiseEvent e2(Me, Test)
End Sub
Public Sub TestHandler4(ByVal o As Object, ByVal e As TestEventArgs) Handles Me.e2
MsgBox(e.Name)
MsgBox(e.Age)
End Sub
Public Sub TestHandler5(ByVal o As Object, ByVal e As TestEventArgs) Handles Me.e2
MsgBox(e.Name)
MsgBox(e.Age)
End Sub
End Class
Public Class TestEventArgs
Inherits EventArgs
Public Name As String
Public Age As Integer
End Class
Which event handler is called first? I.e. TestEventHandler4 or TestEventHandler5? Is it possible to configure this?
Event order when invoking a MultiCastDelegate is undefined in the .Net specification. They are typically invoked in the order they are added, in code order in your case, but don't rely on it.
UPDATE
This MSDN article seems to indicate that the invocation order is now guaranteed to be the order they were added. I haven't actually verified this in practice, and I'm not sure what order Auto wireup events are added.
https://msdn.microsoft.com/en-us/library/system.multicastdelegate(v=vs.110).aspx
Related
I am in a project with multiple form.
I create a TicTacToe form here :
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
Dim page As Form = New TicTacToe
page.Show(Me)
End Sub
Here is a TicTacToe form:
Public Class TicTacToe
Public opponent as String
'Some code where user set opponent
Public Function Receive(S As String)
if string = opponent
'Some code
End Function
End Class
I would like to call my function Receive in my main form
If i do:
TicTactoe.Receive(S)
It call a instance of Receive where opponent does not exist.
I would like to find the oppened form of TicTacToe and call Receive
Thanks
Comments in line
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
page.Receive("Joe")
End Sub
'A form level variable to hold a reference to the instance of TicTacToe
'Although vb.net can use default instances, you have created an explicit
'instance of TicTacToe so you need to keep a reference if you want to
'refer to this instance.
Private page As TicTacToe
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
page = New TicTacToe()
page.Show(Me)
End Sub
Partial Public Class TicTacToe
Inherits Form
Public opponent As String
'Functions must be declared as a Type
'If you do not need a return value use a Sub
Public Function Receive(S As String) As String
Dim someString As String = ""
If S = opponent Then
'Do something
End If
'There must be a return Value
Return someString
End Function
End Class
Use this to show the form
Dim page As TicTacToe
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
page = New TicTacToe
page.Show(Me)
End Sub
Then you can use
page.Receive(S)
Edit
To use multiple forms
For Each f As TicTacToe in Application.OpenForms().OfType(Of TicTacToe)
f.Receive (S)
Next
In C#, you'd need a new instance, but as you are in VB, the compiler already does that for you.
What you are currently doing, is creating a new instance of the TicTacToe form and showing it:
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
Dim page As Form = New TicTacToe
page.Show(Me)
End Sub
But you don't save that instance anywhere. Then, in your next piece of code, you are using a different instance, which is the static one created by the compiler:
TicTacToe.Receive(S) // TicTacToe is the static instance
Therefore, you end up calling two different instances, which explains why there is no opponent set.
To get around this problem, do not create a new instance. In your Private Sub MenuTicTacToe, just use the instance created by the compiler, and you won't have this problem, just like this:
Private Sub MenuTicTacToe(ByVal sender As Object, ByVal e As System.EventArgs)
TicTacToe.Show(Me)
End Sub
Hope this helps.
Research tells me that raising an event from the constructor itself is not feasible as the object may not be fully initialised... so where can I fire an event from as soon as the constructor has fired?
One thing you can do is add a method to handle additional post ctor tasks:
Friend Class FooBar
Public Sub New
' your code here
End Sub
Public Sub Create
' do anything you want
End Sub
End Class
Elsewhere:
Friend WithEvents Foo As Foobar
' ...
Foo = New FooBar ' Foo doesnt exist until ctor code executes and the
' code returns to here.
Foo.Create ' do whatever you like, as long as any other
' objects referenced have been created.
The reason calling a sub from the ctor to raise an event wont work with a class is this:
Private Sub SomeEvent(sender As Object, e As EventArgs) Handles Foo.SomeEvent
Console.Beep()
End Sub
the key is Handles Foo.SomeEvent
There is no Foo yet to handle the event. It doesnt crash and there event is raised, but there is no object for the listener to catch/handle the event. Enough of a form is created in InitializeComponents, that it does work with a form.
There might also be an Interface to implement something like this, I know of some for Components, but not classes.
You could use the Load or Show events from the Shown.
Private Sub myForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown
End Sub
or
Private Sub myForm_Load(sender As Object, e As EventArgs) Handles Me.Load
End Sub
You can accomplish this by adding an Action(Of T) parameter to your constructor and invoke the delegate on the very last line.
Public Class Foo
Public Sub New(ByVal action As Action(Of Foo))
'...
'...
'...
If (Not action Is Nothing) Then action.Invoke(Me)
End Sub
End Class
Example
Public Class Form1
Private Sub Button1_Click(sender As Object, ev As EventArgs) Handles Button1.Click
Dim foo1 As New Foo("foo1", AddressOf Me.HandleFooCtor)
Dim foo2 As New Foo("foo2", Sub(f As Foo) MessageBox.Show(f.Name))
End Sub
Private Sub HandleFooCtor(f As Foo)
MessageBox.Show(f.Name)
End Sub
Public Class Foo
Public Sub New(name As String, Optional ByVal action As Action(Of Foo) = Nothing)
'...
'...
'...
Me.Name = name
If (Not action Is Nothing) Then action.Invoke(Me)
End Sub
Public ReadOnly Name As String
End Class
End Class
I am having an issue when trying to delete ListView Items from a second form.
For example, if I use the following command on Form1 it works:
Listview1.SelectedItems(0).Remove
However, if I attempt to remove from Form2 like so:
Form1.Listview1.SelectedItems(0).Remove
I get the following error:
"Invalid argument=value of '0' is not valid for 'index'. Parameter name: index"
I then tried to get a count of items from the listview on Form2 and it gives me a return of 0
Form1.Listview1.Items.Count
I'm not sure what my problem is.
Update
I have posted a brief example of my code (using your suggestion as I can understand it):
frmShowMessages
Private Sub ViewMessage()
Dim frm As New frmViewMailMessage
frm.Show()
End Sub
Public Sub DeleteItem(ByVal index As Integer)
lsvReceivedMessages.Items(index).Remove()
End Sub
frmViewMessage
Private instanceForm as frmShowMessages
Private Sub frmViewMailMessage_Load(sender As Object, e As EventArgs) Handles MyBase.Load
instanceForm = New frmShowMessages()
End Sub
Private Sub cmdDelete_Click(sender As Object, e As EventArgs) Handles cmdDelete.Click
instanceForm.DeleteItem(_index)
End Sub
Hopefully my code can help identify where my issue is.
In VB.net usually you get a default Form instance for each of your Form. Probably you are creating an instance of Form1 and then you are trying to access ListView1 of default instance.
E.g.
Sub ButtonClick()
Dim f As New Form1()
f.Show()
' at this point if you access f's ListView you will get correct count
f.ListView1.Items.Count
' however if you try to access default instance it will NOT have any item
Form1.ListView.Items.Count
End Sub
It means your instance f is NOT equal to default Form1 instance.
Solution can be, make the f variable as class level variable and use it everywhere. Or if Form1 will have only 1 instance, then you can use the default instance everywhere.
Personally I would NOT go with direct control accessing over forms. I would create a Public method which should return the data as list to the caller, in this case your Form2.
UPDATED-2:
As per your given scenario, I am simplifying things for you, and doing implementation using Event.
Public Class frmShowMessages
Private Sub btnOpenMessage_Click(sender As System.Object, e As System.EventArgs) Handles btnOpenMessage.Click
Dim frmView As New frmViewMessage(Me.ListView1.SelectedItems(0).Index)
AddHandler frmView.MessageDeleted, AddressOf DeleteMessageHandler
frmView.Show()
End Sub
Private Sub DeleteMessageHandler(sender As Object, e As frmViewMessage.MessageDeletedEventArgs)
Me.ListView1.Items.RemoveAt(e.MessageIndex)
End Sub
End Class
Public Class frmViewMessage
' a class which will be used for Event communication
Public Class MessageDeletedEventArgs
Inherits EventArgs
Public Property MessageIndex As Integer
Public Sub New(ByVal iIndex As Integer)
MyBase.New()
Me.MessageIndex = iIndex
End Sub
End Class
' main event which will alert the parent that a message deletion should be done
Public Event MessageDeleted As EventHandler(Of MessageDeletedEventArgs)
' private variable that will hold the MessageIndex
Private Property MessageIndex As Integer
' method that is responsible to raise event
Protected Overridable Sub OnMessageDeleted()
RaiseEvent MessageDeleted(Me, New MessageDeletedEventArgs(Me.MessageIndex))
End Sub
' we want to create this Form using the MessageIndex of ListView
Public Sub New(ByVal iMessageIndex As Integer)
Me.InitializeComponent()
Me.MessageIndex = iMessageIndex
End Sub
' the delete button will raise the event to indicate parent that
' a deletion of message should be done
Private Sub btnDelete_Click(sender As System.Object, e As System.EventArgs) Handles btnDelete.Click
Me.OnMessageDeleted()
End Sub
End Class
Public Class B
End Class
Public Class D
Inherits B
End Class
Public Class SomeClass
Public Shared Sub SomeFunction2(Of TGeneric As B)()
'Is there a way that I can tell whether the the Type used
'as TGeneric is of type "B" or "D" without having
'an instance of a class also passed in?
'Reflection? How?
End Sub
End Class
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
SomeClass.SomeFunction2(Of D)()
End Sub
End Class
That depends on what you mean by "is of type B or D".
If you only want to differentiate between someone actually calling SomeFunction2(Of B) and SomeFunction2(Of AnyTypeThatInheritsFromB), then you can just do:
If GetType(B) Is GetType(TGeneric) Then
... they passed in B
Else
... they passed in a subclass
End If
But this seems like a bit of a code smell. Generics are intended for you not to care what the actual type is. What's the reason you need to know?
Basically, I have a custom child form class which has events that will be passed to the parent. In the custom child form, I have a declaration of a "MustInherit" class that inherits the DevExpress User Control Class.
The reason for this, is I have many user controls that derive from this base class, and the child form can have an instance of any one of these controls, and doesnt care which. The only requirement is that the child form can handle the same events from each type of control the same way.
Some watered down code snippets(still pretty long unfortunately):
'''Inherited Class
Public Class ChildControlInheritedClass
'A Button Click event that starts the chain of events.
Private Sub btnMoveDocker_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnConvertToTab.Click
OnMoveToDocker(Me, New ChildGridMoveArgs(Me))
End Sub
End Class
'''Base Class
Public MustInherit Class ChildControlBaseClass
Inherits DevExpress.XtraEditors.XtraUserControl
Public Class ChildGridMoveArgs
Inherits System.EventArgs
Public Sub New(ByVal _ChildControl As ChildControlInheritedClass)
ChildControl = _ChildControl
End Sub
Public ChildControl As ChildControlInheritedClass
End Class
Public Event MoveToDocker(ByVal sender As Object, ByVal e As ChildGridMoveArgs)
Protected Overridable Sub OnMoveToDocker(ByVal sender As Object, ByVal e As ChildGridMoveArgs)
'''Once this RaiseEvent is fired, nothing happens. The child form is oblivious.
RaiseEvent MoveToDocker(sender, e)
End Sub
End Class
'''Child Form Class
Public Class ChildForm
Private WithEvents cgChild As ChildControlBaseClass
Public Property ChildGrid() As ChildControlInheritedClass
Get
Return cgChild
End Get
Set(ByVal value As ChildControlInheritedClass)
RemoveHandler cgChild.MoveToDocker, AddressOf cgChild_MoveToDocker
cgChild.Dispose()
cgChild = Nothing
cgChild = value
AddHandler cgChild.MoveToDocker, AddressOf cgChild_MoveToDocker
End Set
End Property
Public Event MoveToDocker(ByVal sender As Object, ByVal e As ChildControlInheritedClass.ChildGridMoveArgs)
Public Sub cgChild_MoveToDocker(ByVal sender As Object, ByVal e As ChildControlInheritedClass.ChildGridMoveArgs)
RaiseEvent MoveToDocker(sender, New ChildControlInheritedClass.ChildGridMoveArgs(cgChild))
End Sub
End Class
Public Class frmMain
Private Sub OpenNewWindow()
Dim frm As New ChildForm
Dim chld As New ChildControlInheritedClass
frm.ChildGrid = chld
frm.Show()
End Sub
End Class
In a nutshell, thats how I made the child form and how everything is suppose to work. But when I press the button in the inherited child control, the event only gets as far as the base class and never traverses the RaiseEvent into the child form thats suppose to handle the event.
Am I even in the ballpark here?
Thanks for reading!
You forgot to add your event handle by using AddHandler or Handles identifier. See below using the Handles cgChild.MoveToDocker identifier.
Public Class ChildForm
...
Public Sub cgChild_MoveToDocker(ByVal sender As Object, ByVal e As ChildControlInheritedClass.ChildGridMoveArgs) Handles cgChild.MoveToDocker
RaiseEvent MoveToDocker(sender, New ChildControlInheritedClass.ChildGridMoveArgs(cgChild))
End Sub
End Class