Handling certain events from another class - vb.net

I'm working with a class that has the following function:
Private _players As New List(Of Player)
Public Sub PlayerAdd(ByVal name As String, ByVal money As Decimal)
_players.Add(New Player With {
.name = name,
.money = money})
End Sub
When this sub is called, from a different class, I want to be able to catch this event and run other code like this:
Public foo As New MyCustomClass
Private Sub test Handles foo.PlayerAdded
' Code I want to run here.
End Sub
I know that I cannot do this like that, however it is just to show what I am trying to accomplish. What would be the best way to do something like this?

You can do that as follow:
Public Class MyCustomClass
Private _players As New List(Of Player)
Public Event PlayerAdded()
Public Sub PlayerAdd(ByVal name As String, ByVal money As Decimal)
_players.Add(New Player With {
.name = name,
.money = money})
RaiseEvent PlayerAdded()
End Sub
End Class
And you can catech that event from the other class:
AddHandler foo.PlayerAdded, AddressOf <SomeSub> ' To catch the event

Related

How to serialize a List(of Object) in VB.NET?

I have a class Player that I use to create a List(Of Player). I need to save it when the application closes. In Windows Forms, I would just serialize, but that's no longer possible in UWP, so I had to Google for a few dozen of hours and I finally stumbled upon Microsoft.Toolkit.Uwp, then Newtonsoft.Json, but I fail miserably to use them. I need your help!
Let's say I have a small class :
Dim Name As String
Dim Score As Double
Public Class Player
<JsonConstructor()>
Public Sub New(Name As String, Score As Double) ' New with score
Me.Name = Name
Me.Score = Math.Max(1, Score)
End Sub
End Class
Public Overrides Function ToString() As String ' ToString
Return $"{Name} [{Score}]"
End Function
How do I successfully read and write a List(Of Player)?
' Loading MainPage.xaml
Private Sub MainPage_Loading() Handles Me.Loading
ReadAsync()
MainFrame.Margin = New Thickness(0)
Window.Current.Content = MainFrame
MainFrame.Navigate(GetType(PlayerList), Players)
End Sub
' Read
Private Async Sub ReadAsync()
Players = JsonConvert.DeserializeObject(Of List(Of Player))(Await FileIO.ReadTextAsync((Await StorageFolder.CreateFileAsync("players.json", CreationCollisionOption.OpenIfExists))))
If Players Is Nothing Then
Players = New List(Of Player)
WriteAsync()
End If
End Sub
' Write
Public Shared Async Sub WriteAsync()
Await FileIO.WriteTextAsync(Await StorageFolder.CreateFileAsync("players.json", CreationCollisionOption.ReplaceExisting), JsonConvert.SerializeObject(Players, Formatting.Indented))
End Sub
' Loading PlayerList.xaml
Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
ListBoxPlayers.Items.Clear()
Players = e.Parameter
For Each Player In Players
ListBoxPlayers.Items.Add(Player)
Next
End Sub
' Adding a new player in the interface
Private Sub ButtonAddPlayer_Click(sender As Button, e As RoutedEventArgs) Handles ButtonAddPlayer.Click
' ...
' Commit
MainPage.Players.Add(New Player(TextBoxName.Text))
ListBoxPlayers.Items.Add(MainPage.Players(MainPage.Players.Count - 1))
MainPage.WriteAsync()
End Sub
So this is all confusing. When I add a player trough the interface, it enters my ListBox like normal. However, when I close the application and I re-open it, I get a handful of empty objects.
I did some angry testing to know more about my problem, turns out I'm not serializing at all, but I probably am deserializing correctly.
I found it, turns out it's in my class Player.
What I used :
Dim Name As String
Dim Score As Double
What worked :
Public Name As String
Public Score As Double
What I should have done :
Public Property Name As String
Public Property Score As Double
I was taught to never set variables as "public" when coding in Java, and I didn't know that Property existed in Visual Basic.

VB.NET Class with no constructor (no new instance can be declared)

I'm making my own message box class (called MessageBoxC, whatever), and like System.Windows.Forms.MessageBox, I want to make my class with no constructors and no possibility to declare a new instance of it.
E.g.:
Public Class MessageBoxC
Public Overloads Sub Show(ByVal message As String)
Me.Message = message
ProcessData() '(*)
Me.ShowDialog()
End Sub
End Class
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
System.Windows.Forms.MessageBox.Show("Hello World!") 'works fine
MessageBoxC.Show("Hello World!") 'works fine
Dim msgBox As New System.Windows.Forms.MessageBox 'and you'll get an error message here (**)
Dim msgBoxC As New MessageBoxC 'no error message
End Sub
End Class
(*) Not important. It just calculates text size (width and height in pixels) to correct form size if needed and the corresponding label gets value of Me.Message property.
(**) This one is what I mean. You cannot make a new instance of a MessageBox class, you'll get following error-message: "Type System.Windows.Forms.MessageBox has no constructors."
Well, my class has also no constructors, but it's possible to declare an instance of it. What's the trick here?
Thanks a lot!
Solved. Thanks to OneFineDay.
Public Class MessageBoxC
Private Sub New()
'Empty
End Sub
Public Overloads Shared Function Show(ByVal message As String) As System.Windows.Forms.DialogResult
Return Show(message, Constants.MyAppName, Constants.messageTitle, MessageBoxCButtons.OK, MessageBoxCIcon.Undefined)
End Function
Public Overloads Shared Function Show(ByVal message As String, _
ByVal caption As String, _
ByVal title As String, _
ByVal buttons As Library.MessageBoxCButtons, _
ByVal icon As Library.MessageBoxCIcon) As System.Windows.Forms.DialogResult
Dim msgBoxC As New CBox(message, caption, title, buttons, icon)
msgBoxC.ShowDialog()
Return msgBoxC.DialogResult
End Function
Private Class CBox
Inherits System.Windows.Forms.Form
Sub New(ByVal message As String, _
ByVal caption As String, _
ByVal title As String, _
ByVal buttons As Library.MessageBoxCButtons, _
ByVal icon As Library.MessageBoxCIcon)
MyBase.New()
InitializeComponent()
Me.Message = message
Me.Text = caption
Me.Title = title
Me.Buttons = buttons
Me.Icon64 = icon
Me.OptimizeMe()
End Sub
End Class
End Class
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
Dim dialogResult As New DialogResult
dialogResult = MessageBoxC.Show("This is a simple message.")
MessageBox.Show(dialogResult.ToString)
End Sub
End Class
If you don't declare any constructors, a default constructor is automatically created (this is a public constructor with no parameters).
To prevent anyone creating an instance of your class, you can create a private constructor, like so:
Public Class MessageBoxC
Private Sub New()
' Prevents anyone creating an instance of this class.
End Sub
End Class
Note that your Show method will need to be declared Shared, otherwise you won't be able to call it. In fact, it would need to be Shared, even with the code you provided.
Here is one way to hide the constructor - mainly because the class in question is not accessible.
Public Class Form1
Private Sub meLoad() Handles Me.Load
'Usage
FooBar.Show("Hi")
End Sub
'...
End Class
Public Class FooBar
Private Sub New()
End Sub
Public Shared Sub Show(message As String)
Dim mbc As New MessageBoxC(message)
mbc.ShowDialog()
End Sub
'MessageBoxC is not exposed outside of Foobar which is the entry point
Private Class MessageBoxC : Inherits Form
'define cTor's as needed
Public Sub New(message As String)
Me.Text = message
End Sub
'define content
End Class
End Class

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.

AddressOf with parameter

One way or another I need to link groupID (and one other integer) to the button I am dynamically adding.. any ideas?
What I can do;
AddHandler mybutton.Click, AddressOf PrintMessage
Private Sub PrintMessage(ByVal sender As System.Object, ByVal e As System.EventArgs)
MessageBox.Show("Dynamic event happened!")
End Sub
What I can't do, but want to;
AddHandler mybutton.Click, AddressOf PrintMessage(groupID)
Private Sub PrintMessage(ByVal groupID as Integer)
MessageBox.Show("Dynamic event happened!" & groupID .tostring)
End Sub
There is no way to do this with AddressOf itself. What you're looking for is a lambda expression.
AddHandler myButton.Click, Function(sender, e) PrintMessage(groupId)
Private Sub PrintMessage(ByVal groupID as Integer)
MessageBox.Show("Dynamic event happened!" & groupID .tostring)
End Sub
You can create your own button class and add anything you want to it
Public Class MyButton
Inherits Button
Private _groupID As Integer
Public Property GroupID() As Integer
Get
Return _groupID
End Get
Set(ByVal value As Integer)
_groupID = value
End Set
End Property
Private _anotherInteger As Integer
Public Property AnotherInteger() As Integer
Get
Return _anotherInteger
End Get
Set(ByVal value As Integer)
_anotherInteger = value
End Set
End Property
End Class
Since VB 2010 you can simply write
Public Class MyButton
Inherits Button
Public Property GroupID As Integer
Public Property AnotherInteger As Integer
End Class
You can access the button by casting the sender
Private Sub PrintMessage(ByVal sender As Object, ByVal e As EventArgs)
Dim btn = DirectCast(sender, MyButton)
MessageBox.Show( _
String.Format("GroupID = {0}, AnotherInteger = {1}", _
btn.GroupID, btn.AnotherInteger))
End Sub
These new properties can even be set in the properties window (under Misc).
The controls defined in the current project automatically appear in the toolbox.
Use the Tag property of the button.
Button1.Tag = someObject
AddressOf gets the address of a method, and thus you cannot pass parameters to it.
You can use delegate which very clear for your code follow as:
Define a delegate
Public Delegate Sub ControlClickDelegate(ByVal sender As Object, ByVal e As EventArgs)
Custom button class
Public Class CustomButton
Inherits System.Windows.Forms.Button
#Region "property delegate"
Private controlClickDelegate As ControlClickDelegate
Public Property ClickHandlerDelegate As ControlClickDelegate
Get
Return controlClickDelegate
End Get
Set(ByVal Value As ControlClickDelegate)
controlClickDelegate = Value
End Set
End Property
#End Region
Public Sub RegisterEventHandler()
AddHandler Me.Click, AddressOf OnClicking
End Sub
Private Sub OnClicking(ByVal sender As Object, e As System.EventArgs)
If (Me.controlClickDelegate IsNot Nothing) Then
Me.controlClickDelegate(sender, e)
End If
End Sub
End Class
MainForm
Public Class MainForm
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.CusButton1.ClickHandlerDelegate = AddressOf Me.btnClick
Me.CusButton1.RegisterEventHandler()
End Sub
Private Sub btnClick(ByVal sender As Object, ByVal e As EventArgs)
Me.TextBox1.Text = "Hello world"
End Sub
End Class
The below worked for me:
Dim bStart = New Button With {.Text = "START"}
AddHandler bStart.Click, Function(sender, e) TriggerProcess(any Long value)
Private Function TriggerProcess(ByVal paramName As Long) As Boolean
' any processing logic
Return True
End Function
My solution:
AddHandler menuItemYear.Items(i).MouseUp, Sub() menu_year(2019)
Private Sub menu_year(ByVal intYear As Integer)
'do something
End Sub
There are few ways to do that depending of the complexity and number of parameters required.
1. Use Tag for adding a complex structure
2. Inherit the the Button class and add the values as class members then populate them before using it. That gives you a lot more flexibility.
If you are using web version
3. You cannot add it to Tag, but for simple values assign it to index use .Attributes.Add("name"). This gets added to the HTML tags and not the Server side. You can then use the index to access a server side structure for complex systems.
4. Use sessions to store values and store the session reference to Name attribute as described above (#3).
No problem ;-)
For example:
Private ComboActionsOnValueChanged As New Dictionary(Of ComboBox, EventHandler)
'somewhere in function
dim del = Sub(theSender, eventArgs)
MsgBox(CType(theSender, ComboBox).Name & " test")
End Sub
ComboActionsOnValueChanged.Add(myCombo, del)
'somewhere else
Dim delTest = ComboActionsOnValueChanged(myCombo)
RemoveHandler myCombo.SelectedValueChanged, delTest
myCombo.DataSource = someDataSource
AddHandler myCombo.SelectedValueChanged, delTest
as we expect, event won't fire after DataSource change in this place

VB.NET: How can I have events return a value like I can in C#?

In C#, I can do this:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Class1 c1 = new Class1();
c1.OnNeedInt += new Class1.NeedInt(c1_OnNeedInt);
int i = c1.GetInt();
}
int c1_OnNeedInt()
{
return 1;
}
}
public class Class1
{
public delegate int NeedInt();
public event NeedInt OnNeedInt;
public int GetInt()
{
return OnNeedInt == null ? 0 : OnNeedInt();
}
}
Notice the line int i = c1.GetInt();. I can't seem to get VB.NET 4.0 to do something similiar. Any help?
I thinks its even easier than most people think...
Class MyClass
Public Event MyEvent(ByRef MyVariable as String)
Private Sub DoSomething()
Dim SomethingINeed as String = String.Empty
RaiseEvent MyEvent(SomethingINeed)
'SomethingINeed will now contain "Goodbye Cruel World"
End sub
End Class
Then in the class that monitors the event...
Class MyOtherClass
Private Sub New()
AddHandler MyClass.MyEvent, Addressof MyEventHandler
End Sub
Private Sub MyEventHandler(ByRef StringToPassBack as String)
StringToPassBack = "Goodbye Cruel World"
End Sub
End Class
It's all about the ByRef keywords in both the event declaration and the eventhandler sub.
That's not possible in vb.net, events must be raised with the RaiseEvent statement. It doesn't return a value. It is a pretty questionable practice anyway, an event can have zero or multiple subscribers. No telling what the return value might be. Just use a delegate instead:
Class Page
Public Sub New()
Dim obj As New Class1
Dim dlg As New Func(Of Integer)(AddressOf obj.GetInt)
Dim i As Integer = dlg()
End Sub
End Class
Class Class1
Public Function GetInt() As Integer
Return 42
End Function
End Class
In VB, you don't need to check to see if anyone is attached to your event handler. You can just call RaiseEvent and if anyone is listening to it, it will work. However, the event isn't intended to return a value. You could try sticking it into an event arg and pass that around, but that gets messy.
#HansPassant's solution is close, but not quite what you were asking for. Altering his solution a bit:
Delegate Function FetchIt() As Integer
Class Page
Public Sub New()
Dim obj As New Class1
Dim i As Integer = obj.GetInt(AddressOf c1_OnNeedInt)
End Sub
Function c1_OnNeedInt() As Integer
Return 42
End Function
End Class
Class Class1
Public Function GetInt(fetcher As FetchIt) As Integer
Return fetcher()
End Function
End Class
Alternatively, you could do this without the custom delegate using Lambda's:
Class Page
Public Sub New()
Dim obj As New Class1
Dim dlg As New Func(Of Integer)(AddressOf c1_OnNeedInt)
Dim i As Integer = obj.GetInt(dlg)
End Sub
Function c1_OnNeedInt() As Integer
Return 42
End Function
End Class
Class Class1
Public Function GetInt(fetcher As Func(Of Integer)) As Integer
Return fetcher()
End Function
End Class
I found an answer to my issue. In the base class that my ASP.NET user controls inherit, I have this:
Dim _Connection As MyConnection
Public Property Connection As MyConnection
Get
If _Connection Is Nothing Then
RaiseEvent OnNeedConnection(_Connection)
End If
Return _Connection
End Get
Set(value As MyConnection)
_Connection = value
End Set
End Property
Public Delegate Sub NeedConnection(ByRef Connection As MyConnection)
Public Event OnNeedConnection As NeedConnection
In my web form codebehind, I wire it up manually to this:
Sub ServeConnection(ByRef Connection As MyConnection)
Connection = oConn
End Sub
The actual connection is hosted on the webform's codebehind, but I have several user controls that need to use this connection. Any time any of the user controls need the connection, their base class requests it and the host page serves it. This is made possible by the ByRef keyword.
This is the closest C# equivalent I could put together.