Sharing View Model instance in different collections - vb.net

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.

Related

Custom typed, non-generic, read-only, fixed-sized collection class in VB.Net?

I'm looking for suggestions on how to create a custom collection class in VB.Net, to contain instances of a custom object class. There is so much information on the topic that I'm not sure which direction to go in, and custom collections are new to me.
The collection needs are as follows:
collection should be read-only, it and its objects cannot be modified.
collection will always be a fixed size.
because of above two items, add, remove, count, clear, etc aren't needed.
will create & manage all object instances itself at instantiation.
should have a default property like "Item". (?)
should be enumerable, so For-Each can be used on it.
Here is a simplified outline of what I've pieced together so far:
Class Bay
Private ID As Integer
Private p_String As String
Private p_Aisle As Integer
...etc...
...getters, setters, & subs...
End Class
Class Bays
Inherits ...something...
Implements ....somethingelse... (?)
Public ReadOnly MyCollection(5094) as SomeCollectionType (Of Bay)
Private LastUpdate As Date
Private SystemStatus as Integer
Public Sub New()
...instantiate all objects in collection...
End Sub
...properties...
End Class
This is the sort of thing you can do:
Public Class Thing
'...
End Class
Public Class ReadOnlyThingCollection
Inherits ReadOnlyCollection(Of Thing)
Public Sub New()
MyBase.New(GetItems())
End Sub
Private Shared Function GetItems() As IList(Of Thing)
'Generate items as appropriate.
Return {New Thing, New Thing, New Thing}
End Function
End Class

VB.NET constructors in derived classes

I have a base class that I use with reflection to fill the fields of the derived classes, reading from the database.
Public MustInherit Class DaoBase : Implements IEquatable(Of DaoBase)
Sub New()
' Empty, hate to have it
End Sub
Sub New(reader As DbDataReader)
' Reads the DB and fills the instance fields
End Sub
' More stuff...
End Class
The derived classes usually have a non-default constructor to set its fields:
Public Class Customer
Inherits DaoBase
Public Sub New(
id As Integer,
description As String)
Me.id = id
Me.description = description
End Sub
End Class
Questions:
1) I don't like to have the empty constructor in the base class. It sits there unused and could create an object in an incorrect state. If I remove it, then the compiler gives an error because, missing the default constructor, the derived class constructor should call the only-one base class constructor.
2) I can't do new Customer(myReader) because that constructor is not in the derived class, even if it's in the base class. I have to explicitly declare it, which I don't like.
Public Class Customer
Inherits DaoBase
Public Sub New(
id As Integer,
description As String)
Me.id = id
Me.description = description
End Sub
Public Sub New(reader As DbDataReader)
MyBase.New(reader)
End Sub
End Class
If your base class is filling fields in the derived class, it sounds like you should be using an interface instead of what you're doing.
As for your questions, just because you don't like it doesn't make it wrong. But as one comment said, if you change the second New to:
Sub New(Optional reader as DbDataReader = Nothing)
then you fulfill the requirement to have an empty constructor and you can have it do the right thing when no reader is given.

Bubble up events from a one-to-many relationship

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.

How to implement class constructor in Visual Basic?

I just would like to know how to implement class constructor in this language.
Not sure what you mean with "class constructor" but I'd assume you mean one of the ones below.
Instance constructor:
Public Sub New()
End Sub
Shared constructor:
Shared Sub New()
End Sub
Suppose your class is called MyStudent. Here's how you define your class constructor:
Public Class MyStudent
Public StudentId As Integer
'Here's the class constructor:
Public Sub New(newStudentId As Integer)
StudentId = newStudentId
End Sub
End Class
Here's how you call it:
Dim student As New MyStudent(studentId)
Of course, your class constructor can contain as many or as few arguments as you need--even none, in which case you leave the parentheses empty. You can also have several constructors for the same class, all with different combinations of arguments. These are known as different "signatures" for your class constructor.
If you mean VB 6, that would be Private Sub Class_Initialize().
http://msdn.microsoft.com/en-us/library/55yzhfb2(VS.80).aspx
If you mean VB.NET it is Public Sub New() or Shared Sub New().
A class with a field:
Public Class MyStudent
Public StudentId As Integer
The constructor:
Public Sub New(newStudentId As Integer)
StudentId = newStudentId
End Sub
End Class

Generic Collections, Member Classes, Design Pattern question for VB.NET

I have a class called Person:
Public Class Person
Private PersonID as String
Private Name as String
Private Records as GenericCollection(Of PublicRecord)
Public Sub New(ByVal ID as String)
Me.PersonID = ID
Me.Name = getPersonName(ID)
End Sub
'Get/Sets
End Class
getPersonName is simply a function that does exactly as it is described. GenericCollection class is as follows:
Public Class GenericCollection(Of ItemType)
Inherits CollectionBase
' Purpose: Provides a generic collection class from which all other collections
' classes can be inherited if they wish to extend the functionality below.
#Region "Public Methods"
Public Function Add(ByVal NewObject As ItemType) As Integer
Return MyBase.InnerList.Add(NewObject)
End Function
Public Sub New()
MyBase.New()
End Sub
#End Region
#Region "Public Properties"
Default Public Property Item(ByVal Index As Integer) As ItemType
Get
Return CType(MyBase.InnerList(Index), ItemType)
End Get
Set(ByVal value As ItemType)
MyBase.InnerList(Index) = value
End Set
End Property
#End Region
End Class
PublicRecord class is:
Public Class PublicRecord
Private RecordID As String
Private RecordDataOne As String
Private RecordDataTwo As String
Public Sub New()
MyBase.New()
End Sub
'Get/Sets
End Class
One of the requirements I've been told can be done is that I should be able to grab all Persons in a Collection of Persons, then since all of those Persons will have Collectinos of Records within them... grab a specific set of data from the Collection of Records.
We'll say, I want to: getPersonsOverAge21() from the Collection of Records inside each Person inside the Collection of Persons.
Is this even possible? If so, can someone explain how it would work?
There's no need to implement your own generic collection class. .Net has already done this for you in the System.Collections.Generic namespace. Look at a List(Of Person) or even just a simple IEnumerable(Of Person).
Now you haven't explained how your record objects relate to your person type or what data they contain, so I can only speculate on the next part. But it sounds kind of like you want something like this:
Dim people As List(Of Person) = GetPeopleFromDatabase()
Dim peopleOver21 As IEnumerable(Of Person) = people.Where(Function(p) p.Age >= 21)
Dim peopleOver21Query = From p In people _
Where (p.Age >= 21) _
Select p