Bubble up events from a one-to-many relationship - vb.net

I can bubble up an event from a one-to-one relationship like this
Public Class Husband
Public WithEvents Wife As Wife
Public Sub WifeChangedLastName() Handles Wife.LastNameChanged
MsgBox("Wife Changed Last Name")
End Sub
End Class
Public Class Wife
Public _LastName As String
Public Property LastName As String
Get
Return Me._LastName
End Get
Set(ByVal Value As String)
Me._LastName = Value
Raise Event LastNameChanged(Me, EventArgs.Empty)
End Set
End Property
Public Event LastNameChanged As EventHandler
End Class
But how do I do something similar with a one-to-many relationship? Here's what I have so far:
Public Class Organization
Public WithEvents Group As New Group 'A one-to-one relationship
Public Sub PersonAddedToGroup() Handles Group.PersonAdded
MsgBox("A person has been added to the group.") 'This works
End Sub
'I want to do something here when a person's name changes
End Class
Public Class Group
Public WithEvents People As List(Of Person) 'A one-to-many relationship
Public Sub Add(ByVal Person As Person)
Me.People.Add(Person)
RaiseEvent PersonAdded(Me, EventArgs.Empty)
End Sub
Public Event PersonAdded As EventHandler
End Class
Public Class Person
Private _Name As String
Public Property Name As String
Get
Return Me._Name
End Get
Set(ByVal Value As String)
Me._Name = Value
RaiseEvent PersonChanged(Me, EventArgs.Empty)
End Set
End Property
Public Event PersonChanged As EventHandler
End Class
I'd like to handle a PersonChanged event inside Organization. How do I do this?

You'll need to add the event-handler for each person... they can all be handled by the same method though. Here's what I'd suggest.
Change the PersonAdded event to pass the new Person object that was added. You'll need to update where you declared the event/handler to include this, I believe...
'Inside Group.Add(person As Person)
RaiseEvent PersonAdded(Me, person)
In the event handler for PersonAdded, subscribe to the PersonChanged event for that particular person:
Public Sub PersonAddedToGroup(person As Person) Handles Group.PersonAdded
MsgBox("A person has been added to the group.") 'This works
AddHandler person.PersonChanged, AddressOf OnPersonChanged
End Sub
Something like that should accomplish what you want (this is rough code, not testing in VS). If you are adding and removing people, remember that events can lead to memory leaks (i.e. you'll want to call RemoveHandler when the object subscribing to the events goes away. In this case Organization will probably outlast the Person object, so it's not that much of an issue, if I'm not mistaken.

I found an answer from LarsTech. He suggested using a (System.ComponentModel.)BindingList which handles property change events.

Related

DataBind a Simple String Property to Textbox

I have a Simple Property called Customer as string
I want to bind this property to a Textbox.Text Databinding
I use the INotifyPropertyChanged Interface.
If I want to add the Databindings with
TextBox1.DataBindings.Add("Text", Customer, "Text")
I get an Error with:
You cannot bind text to the property or column for the DataSource.
Parameter name: dataMember
Public Class Form1
Implements INotifyPropertyChanged
Private _Customer As String = "DEFAULT"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
TextBox1.DataBindings.Add("Text", Customer, "Text")
End Sub
Public Property Customer As String
Get
Return _Customer
End Get
Set
_Customer = Value
NotifyPropertyChanged()
End Set
End Property
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
The reason is that you can't bind to a property directly, you need to bind to an object which contains the property. Also you can't use a property that is inside the Form1 class. You need to set up an instance of an object.
I've created a sample that uses a class named Customer with a single property called Name. I've also created a base class. This is not required, but is useful if you create multiple classes.
Public Class BaseNotify
Implements INotifyPropertyChanged
Friend Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Friend Sub NotifyPropertyChanged(<CallerMemberName()> Optional propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Public Class Customer
Inherits BaseNotify
Private _name As String = "DEFAULT"
Public Property Name As String
Get
Return _name
End Get
Set
If (_name = Value) Then Return
_name = Value
NotifyPropertyChanged()
End Set
End Property
End Class
Finally set up the form (I also renamed the textbox to something more meaningful.
Public Class Form1
Private _customer As Customer
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
BindProperties()
End Sub
Private Sub BindProperties()
_customer = New Customer()
Me.tbName.DataBindings.Add("Text", _customer, NameOf(Customer.Name))
End Sub
End Class
Using NameOf is recommended, as it won't break the code if you decide to change the property name at a later stage.

Sharing View Model instance in different collections

Are there best practices for creating and sharing a View Model instance between different collection objects? For example, here's a scenario of School and Class classes that both have collections of Student objects:
Public Class Student
Public Property FirstName As String
Public Property LastName As String
Public Property Id As Integer
End Class
Public Class [Class]
Public Property Id As Integer
Public Property Name As String
Public Property Students As New Dictionary(Of Integer, Student)
Public Event StudentAdded(sender As Object, e As StudentEventArgs)
Public Sub AddStudent(student As Student)
If Not Students.ContainsKey(student.Id) Then
Students.Add(student.Id, student)
RaiseEvent StudentAdded(Me, New StudentEventArgs(student))
End If
End Sub
End Class
Public Class School
Public Property Classes As New Dictionary(Of Integer, [Class])
Public Property Students As New Dictionary(Of Integer, Student)
Public Event ClassAdded(sender As Object, e As EventArgs)
Public Event StudentAdded(sender As Object, e As StudentEventArgs)
Public Sub AddClass([class] As [Class])
If Not Classes.ContainsKey([class].Id) Then
Classes.Add([class].Id, [class])
RaiseEvent ClassAdded(Me, EventArgs.Empty)
End If
End Sub
Public Sub AddStudent(student As Student)
If Not Students.ContainsKey(student.Id) Then
Students.Add(student.Id, student)
RaiseEvent StudentAdded(Me, New StudentEventArgs(student))
End If
End Sub
End Class
Public Class StudentEventArgs
Inherits EventArgs
Public Property Student As Student
Public Sub New(student As Student)
Me.Student = student
End Sub
End Class
When creating a View Model for School and Class, both could end up creating View Models for the same Student objects:
Public Class SchoolViewModel
Inherits BaseViewModel
Private WithEvents _school As School
Public Sub New(school As School)
_school = school
End Sub
Public Property Students As New ObservableCollection(Of StudentViewModel)
Public Property Classes As New ObservableCollection(Of ClassViewModel)
Private Sub _school_StudentAdded(sender As Object, e As StudentEventArgs) Handles _school.StudentAdded
' Create a new View Model for the Student
Students.Add(New StudentViewModel(e.Student))
End Sub
End Class
Public Class ClassViewModel
Inherits BaseViewModel
Private WithEvents _class As [Class]
Public Sub New([class] As [Class])
_class = [class]
End Sub
Public Property Students As New ObservableCollection(Of StudentViewModel)
Private Sub _class_StudentAdded(sender As Object, e As StudentEventArgs) Handles _class.StudentAdded
' Create a new View Model or try to get the one created by the SchoolViewModel?
Students.Add(New StudentViewModel(e.Student))
End Sub
End Class
Public Class StudentViewModel
Inherits BaseViewModel
Private _student As Student
Public Sub New(student As Student)
_student = student
With _student
Me.FirstName = .FirstName
Me.LastName = .LastName
Me.Id = .Id
End With
End Sub
Public Property FirstName As String
Public Property LastName As String
Public Property Id As Integer
End Class
My question is regarding the creation of StudentViewModel instances in both SchoolViewModel and ClassViewModel StudentAdded event handlers. Let's assume that the students are first added to the School and then assigned to their classes. The StudentViewModel is pretty simple and creating duplicates might not be a problem but it nonetheless seems wasteful when a View Model for the same student has already been created in the SchoolViewModel.
If I want to share the SchoolViewModel's StudentViewModel, what's a best practices approach for that?
Passing a reference to SchoolViewModel's Students ObservableCollection when creating a new ClassViewModel?
Creating some GetStudentViewModelDelegate function?
Creating a Shared collection of StudentViewModel?
I usually try to limit references between objects and creating new View Models would be a tradeoff for that in this example.
Although I can't help specifically in VB, but you may be best looking into GENERICS.
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/generic-types
It is a way to create more of a top-level class where the internal operations represent a given "type". Ex: Student, School, Subject (instead of a Class type named [Class]. I just see ALL types of confusion downstream.
The article covers some basics, but I think is what you are looking for. You would want the context of things and functions generic too, so no matter what "type" you want, you can nest common functionality.
For example, for each of your student, class, school, you have an "AddStudent", "AddClass", "AddSchool", or similar. If you have a generic with an "Add" method, then it would have the "Add" place-holder / operations once. Then, if you have this ViewModel working as type "Student", and another of "School" that HAS an instance of ViewModel "Student", you could do something like
SchoolObject.StudentObject.Add()
vs
SchoolObject.Add()
The "Of" type can also be declared as a class type that qualifies based on an "Interface" or some parentClassType too. So, if you have a base-class that is common to all Student, Class (Subject), School, then you can generic apply to the INTERFACE. So, please forgive my basic capacities at VB.
https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/interfaces/walkthrough-creating-and-implementing-interfaces
Hopefully this may help you in utilizing a concept to better fit your need that you just were not familiar with.

Winform TextBox databind to Listbox using INotifyPropertyChanged

I have set up Entity Framework with my VB.NET project. I have a model class that takes the data from a table from my MS SQL Server DB. My ListBox object is filled from the model class. What I am trying to do is when a user clicks on an item on from the listbox the text box is populated with the data from the table. If the user clicks on a mustang the text boxes are filled with the model, make, and year of a mustang.
I am using INotifyPropertyChanged in a viewmodel class that I thought would allow me to get each part of the dataset.
This code shows only one part of the requirements to better simplify the post
Public Class CarViewModel
Implements INotifyPropertyChanged
Private _SelectedCarModelName As CarModel
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property SelectedCarModelName () As CarModel
Get
Return _SelectedCarModelName
End Get
Set(ByVal value As CarModel)
_SelectedCarModelName = value
OnPropertyChanged("CarName")
End Set
End Property
Public Overridable Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
My Textbox Data bind looks like this. My Textbox resides on the frm.vb.
txtCarName.DataBindings.Add("Text", _carViewModel.SelectedCarModelName.CarName, "CarName", True, DataSourceUpdateMode.OnPropertyChanged)
When I click on an item on the list it will access the property, but it will not set the property change. I know I am doing something wrong but I just don't know what it is.
******UPDATED*******
ViewModel
Public Class CarViewModel
Implements INotifyPropertyChanged
Private _SelectedCarModelName As CarModel
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property SelectedCarModelName As CarModel
Get
Return _SelectedCarModelName
End Get
Set(ByVal value As CarModel)
_SelectedCarModelName= value
OnPropertyChanged("SelectedCarModelName ")
End Set
End Property
Public Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Windows Form TextBox
**************Edited*******************
private _CarViewModel As CarViewModel
Dim bs As New BindingSource(_CarViewModel , Nothing)
txtCarName.DataBindings.Add _
("Text", bs, "SelectedCarModelName", True, DataSourceUpdateMode.OnPropertyChanged, String.Empty)
After I have restarted everything, for some reason my machine likes that, I am not getting an error anymore. When I debug the code is iterating through the Getter 3x, the number of fields. but it is not getting an output for the textbox.

How to update UI when a property change

I'm trying to update my UI when a property in my BL class changes. Please can someone advise the best way to do this in vb.net
Not a really precise question so I will explain the standard way (in my opinion).
Implement the INotifyPropertyChanged interface in your class and handle the PropertyChanged event of your object.
First the the class of the object that contains the property in question:
Public Class MySweetClass
Implements System.ComponentModel.INotifyPropertyChanged
Private _MyProperty As String
Public Property MyProperty As String
Get
Return _MyProperty
End Get
Set(value As String)
_MyProperty = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("MyProperty"))
End Set
End Property
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Notice that the PropertyChanged event is raised once the value of the property changes.
In your form handle this event:
Public Class Form1
Private WithEvents MySweetObject As MySweetClass
Private Sub MySweetObject_PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Handles MySweetObject.PropertyChanged
'Update gui here
End Sub
End Class
This lets you update the GUI whenever the value changes.

Handling Parent Property Event

Is it possible to listen to a parent class' object's event via the property accessor?
What I've tried (a minimal example):
Public Class ParentFoo
Private WithEvents m_bar As EventyObj
Public Property Bar() As EventyObj
Get
Return m_bar
End Get
Set(ByVal value As EventyObj)
m_bar = value
End Set
End Property
End Class
Public Class ChildFoo
Inherits ParentFoo
[...]
Public Sub Bar_OnShout() Handles Bar.Shout
' Some logic
End Sub
End Class
The specific error message I'm getting (VS2005) is "Handles clause requires a WithEvents variable defined in the containing type or one of its base types." Does accessing a private WithEvents variable via a public property strip away the 'WithEvents'?
In ParentFoo:
Public Overridable Sub OnShout() Handles m_bar.Shout
'No Logic Necessary
End Sub
In ChildFoo:
Public Overrides OnShout()
'Logic Here
End Sub
Since ParentFoo will call OnShout when m_bar raises a Shout Event and you override it in ChildFoo, your ChildFoo's OnShout will handle that event.