VB.NET Object with custom name to store property? - vb.net

I'm not familiar with the type of structure or whatever I need to use to achieve this, but I know that there is one.
I'm trying to make it so that I can reference things something like this:
racerlist(x).compatibilityArr.john.CleatScore
instead of what I have to do now:
racerlist(x).compatibilityArr.CleatScoreArr(y).name/.score
So essentially, I want to add items to the compatibilityarr (will probably have to change to a list which is fine) and be able to reference the racer as their own name, instead of by using an index.

This is one way to build a solution that fits your needs as described above. It requires an embedded class that is built as a List(Of T) where we overload the property to accept a string rather than the integer.
Public Class Foo
Public Property compatibilityArr As New Members
End Class
Public Class Members : Inherits List(Of Member)
Public Overloads ReadOnly Property Item(name As String) As Member
Get
Return Me.Where(Function(i) i.Name = name).FirstOrDefault
End Get
End Property
End Class
Public Class Member
Public Property Name As String
Public Property CleatScore As Integer
End Class
Then to use it:
Public Class Form1
Dim f As New Foo
Private Sub loads() Handles Me.Load
Dim member As New Member With {.Name = "John", .CleatScore = 10}
f.compatibilityArr.Add(member)
MessageBox.Show(f.compatibilityArr.Item("John").CleatScore)
End Sub
End Class

There are other ways to do this, but the simplest is to write a function to search the array by name:
Sub Main1()
Dim racerlist(2) As Racer
racerlist(0) = New Racer With {.Name = "Adam", .CleatScore = "1"}
racerlist(1) = New Racer With {.Name = "Bill", .CleatScore = "2"}
racerlist(2) = New Racer With {.Name = "Charlie", .CleatScore = "3"}
For i As Integer = 0 To racerlist.GetUpperBound(0)
For j As Integer = 0 To racerlist.GetUpperBound(0)
If racerlist(j).Name <> racerlist(i).Name Then
ReDim Preserve racerlist(i).CompatibilityArr(racerlist(i).CompatibilityArr.GetUpperBound(0) + 1)
racerlist(i).CompatibilityArr(racerlist(i).CompatibilityArr.GetUpperBound(0)) = racerlist(j)
End If
Next j
Next i
Dim racerBill As Racer = Racer.FindRacer(racerlist, "Bill")
MsgBox(racerBill.FindCompatibility("Charlie").CleatScore)
End Sub
Class Racer
Property Name As String
Property CleatScore As String
Property CompatibilityArr As Racer()
Sub New()
ReDim CompatibilityArr(-1) 'initialise the array
End Sub
Function FindCompatibility(name As String) As Racer
Return FindRacer(CompatibilityArr, name)
End Function
Shared Function FindRacer(racerlist() As Racer, name As String) As Racer
For i As Integer = 0 To racerlist.GetUpperBound(0)
If racerlist(i).Name = name Then
Return racerlist(i)
End If
Next i
Return Nothing
End Function
End Class

As #Codexer mentioned, I used a dictionary to achieve this.
In my list of Racers (RacerList), I have RacerCompatibility, which I created similar to below:
Public RacerCompatibility As New Dictionary(Of String, Compatibility)
Compatibility is created like:
Public Class Compatibility
Public Cleat As Boolean
Public Skill As Integer
Public Height As Integer
End Class
So now I can access the compatibility of a racer inside the list like:
RacerList(x).RacerCompatibility.Item("John")

Related

Filter List of Class by Property Value and List Of Value

I have a Devexpress Gridcontrol bound to a custom class.
The class looks like this:
Public Class AuditList
Public CasualtyList As List(Of CasualtyRecords)
Public MedsList As List(Of CasualtyRecords.Medications)
Public Property FilterString As CriteriaOperator
Public Sub New()
CasualtyList = New List(Of CasualtyRecords)
MedsList = New List(Of CasualtyRecords.Medications)
End Sub
Public Class CasualtyRecords
Private _primary As New PS
Public Property PrimarySurvey As PS
Get
Return _primary
End Get
Set(value As PS)
_primary = value
End Set
End Property
Public Sub New()
Vitals = New List(Of VitalRecords)
End Sub
Public Property Vitals As List(Of VitalRecords)
Public Property Meds As List(Of Medications)
ReadOnly Property MedCount As Integer
Get
Return Meds.Count
End Get
End Property
Property Id As Integer
Property ClinicalImpression As String
Property Disposal As String
Property Age As Integer
Property Gender As String
Class PS
Public Property Airway As Integer
Public Property Breathing As Integer
Public Property Circulation As Integer
Public Property Rate As Integer
End Class
Class Medications
Public Property MedName As String
End Class
End Class
End Class
This is an example of a filter type I am trying to create:
"[Gender] ='Male' AND [Medications].[MedName] = 'Paracetamol' AND [Age] >100"
Is this possible with the class constructed as shown, or perhaps do I need to implement some other interface?
I imagine that it would look something like this with LINQ
Dim b As New CasualtyRecords
b = a.CasualtyList.Where(Function(x) x.Meds.Any(Medications.Med = "Paracetamol") And x.Gender = "Male" And x.Age > 20)
Thanks
I was able to achieve the required results using this LINQ query
Dim newrecords = a.CasualtyList.Where(
Function(x) x.Meds.Any(
Function(b) b.MedName = "Paracetamol") _
And x.Gender = "Male" And x.Age > 20).ToList()

Collection class of specific type containing basic features

Every time i use some class e.g Artikel as follows:
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
End Class
For such classes i would like to have collection class. The features i would like to have is like:
--> Add (passing Artikel object)
--> Remove (passing Artikel object)
--> Sort entire collection (based on Position property desc/asc)
--> Compare two Artikels (pass by Artikels and tell by which property has to be compared)
--> Check whether two artikels equals
--> Every added artikel has to be marked by Key (so maybe dictionary)? <key><Artikel>
--> Remove Artikel (passing by Key index)
Could somone from you there tell me or even better provide example of collection class pass those requirments?
EDIT: Startup:
Artikel's collection:
Option Strict On
Public Class Articles
Public Property collection As Dictionary(Of Integer, Artikel)
Sub New()
'Initiate new collection
collection = New Dictionary(Of Integer, Artikel)
End Sub
'Add new Artikel to collection
Public Function AddToCollection(ByVal artikel As Artikel) As Boolean
collection.Add(artikel)
Return True
End Function
'Remove specific Artikel
Public Sub RemoveFromCollectionByArtikel(artikel As Artikel)
If Not IsNothing(collection) Then
collection.Remove(artikel)
End If
End Sub
'Get collection
Public Function GetCollection() As Dictionary(Of Integer, Artikel)
Return collection
End Function
'Sort collection by property position
Public Sub SortByPosition()
collection.Sort()
End Sub
'Remove specific sending keys and then reorder them
Public Sub RemoveAllMarkedAsDeleted(keys As List(Of Integer))
'-- Check whther anything has been marked as deleted
If keys.Count > 0 Then
For Each row In keys
collection.Remove(row)
Next
ReorderKeys()
End If
'Reorder all Artikels in collection
Private Sub ReorderKeys()
Dim newCollection As New Dictionary(Of Integer, Artikel)
Dim index As Integer = 0
For Each collitem In collection
newCollection.Add(index, collitem.Value)
index += 1
Next
collection.Clear()
collection = newCollection
End Sub
End Class
Artikel class (additionally i implemented IComparable to be able to sort)
Option Strict On
Public Class Artikel
Implements IComparable(Of Artikel)
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
Public Function CompareTo(pother As Artikel) As Integer Implements IComparable(Of Artikel).CompareTo 'we can sort because of this
Return String.Compare(Me.Position, pother.Position)
End Function
Public Shared Function FindPredicate(ByVal partikel As Artikel) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel.ID = partikel2.ID
End Function
Public Shared Function FindPredicateByUserId(ByVal partikel As String) As Predicate(Of Artikel)
Return Function(partikel2 As Artikel) partikel = partikel2.ID
End Function
End Class
Parts of it look good, but I would ultimately do it a bit differently. First, consider overloads on the item class to make them easier to create and default initialization:
Public Class Article
Property ID As Integer = -1
Property Key As String = ""
Property Name As String = ""
Property Position As Integer = -1
Property PubDate As DateTime = DateTime.Minimum
Public Sub New()
End Sub
' whatever minimum data a new item requires
Public Sub New(k As String, n As String)
Key = k
Name = n
End Sub
' full initialization:
Public Sub New(k As String, n As String, pos As Int32,
pubDt As DateTime)
...
End Sub
End Class
I added some properties for variety, and I suspect "Nummer" might be the "Key" mentioned in the OP, but whatever it is, I would add it to the Article class as that name, if it has some importance.
You might need a simple ctor for serialization (???). Some of these will find and use a Private parameterless constructor, but your code will be forced to use one of the overloads in order to provide some minimum level of data when a new one is created.
You probably do not need IComparable. That is typically for more complex comparisons, such as multiple or complex properties. An example is a carton or box:
If (width = Other.Width) AndAlso (height = Other.Height) Then
Return 0
ElseIf (width = Other.Height) AndAlso (height = Other.Width) Then
Return 0
End If
Plus more gyrations to work out which is "less" than the other. One reason you dont need it, is because If Art1.Postion > Art2.Postion is trivial. The other reason in your case, is because a Dictionary cannot be sorted.
Rather than a Dictionary, an internal List would work better for some of the things you describe but still allow you to have it act like a Dictionary to the extent you need it to. For this, I might build it using ICollection<T>:
Public Class ArticleCollection
Implements ICollection(Of Article)
Pressing Enter after that line will add all the required methods including:
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
Public Sub Clear() Implements ICollection(Of Article).Clear
Public Function Contains(item As Article) As Boolean Implements ICollection(Of Article).Contains
Public ReadOnly Property Count As Integer Implements ICollection(Of Article).Count
Public Function Remove(item As Article) As Boolean Implements ICollection(Of Article).Remove
It remains completely up to you how these are implemented. It also doesn't rule out adding methods such as RemoveAt(int32) or RemoveByKey(string) depending on what you need/how it will be used. One of the benefits to ICollection(Of T) is that it includes IEnumerable which will allow use for each loops (once you write the Enumerator): For Each art In Articles
To emulate a dictionary to allow only one item with a specific property value:
Public Class ArticleCollection
Implements ICollection(Of Article)
Private mcol As List(Of Article)
...
Public Sub Add(item As Article) Implements ICollection(Of Article).Add
' check for existing key
If KeyExists(item.Key) = False Then
mcol.Add(item)
End If
End Sub
You can also overload them:
' overload to match Article ctor overload
Public Sub Add(key As String, name As String)
If KeyExists(key) = False Then
' let collection create the new item
' with the minimum required info
mcol.Add(New Article(key, name))
End If
End Sub
If you add an Item Property, you can index the collection ( Articles(3) ):
Property Item(ndx As Int32) As Article
Get
If ndx > 0 AndAlso ndx < mcol.Count Then
Return mcol(ndx)
Else
Return Nothing
End If
End Get
Set(value As Article)
If ndx > 0 AndAlso ndx < mcol.Count Then
mcol(ndx) = value
End If
End Set
End Property
' overload for item by key:
Public Property Item(key As String) As Article
An Add method and an Item Property will be important if the collection will display in the standard NET CollectionEditor.
There are several ways to implement sorting. The easiest is to use linq in the code which uses your collection:
Articles = New ArticleCollection
' add Article items
Dim ArticlesByDate = Articles.OrderBy(Function(s) s.PubDate).ToList()
Where PubDate is one of the Article properties I added. The other way to handle sorting is by the collection class returning a new collection (but it is so simple to do, there is little need for it):
Friend Function GetSortedList(bSortAsc As Boolean) As List(Of Article)
If bSortAsc Then
Return mcol.OrderBy(Function(q) q.PubDate).
ThenBy(Function(j) j.Position).ToList()
Else
Return mcol.OrderByDescending(Function(q) q.PubDate).
ThenByDescending(Function(j) j.Position).ToList()
End If
End Function
Whether it implements ICollection(Of T), inherits from ICollection(Of T) or does work off a Dictionary depends entirely on what this is, how it is used and whatever rules and restrictions there are (including if it will be serialized and how). These are not things we know.
MSDN has an article on Guidelines for Collections which is excellent.
Create your class
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Create another class which holds a private list and add sub routines to it
Public Class ArtikelList
Private _List as new list (of Artikel)
Public sub remove(Key as integer)
Dim obj as Artikel = nothing
for each x as Artikel in _List
if x.ID = Key then
obj = x
exit for
end if
Next
if not isnothing(obj) then
_List.remove(obj)
end if
End sub
Sub Add(obj as Artikel)
Dim alreadyDeclared as boolean = falsse
for each x as Artikel in _List
if x.ID = obj.id then
alreadyDeclared = true
exit for
end if
Next
if not AlreadyDeclared then
_List.add(obj)
Else
'Somehow inform the user of the duplication if need be.
end if
End sub
End Class
Then use your list class.
dim L as new ArtikelList
L.add(new Artikel(1280, "AFKforever!", "Prof.FluffyButton", 96))
L.remove(1280)
I only added one sub routine as an example. I hope it helps but feel free to ask for more example routines.
This can also be done by creating a class which inherits from the list class, exposing all list class functionality but by using this method you are forced to create every subroutine that will be used. This way you only use routines that you created exclusively for the purpose Artikel objects handling.
Check if two Artikels are equal
Public Class Artikel
Property ID As Integer
Property Nummer As String
Property Name As String
Property Position As Integer
sub new (_ID as integer, _Nummer as string, _Name as string, _Position as integer)
ID = _ID
Nummer = _Nummer
Name = _Name
Position = _Position
End Sub
End Class
Public Overrides Overloads Function Equals(obj As Object) As Boolean
If obj Is Nothing OrElse Not Me.GetType() Is obj.GetType() Then
Return False
else
dim _obj as artikel = obj
if Me.ID = _obj.ID then
Return true
else Return False
End If
End Function
End Class
Use it like:
If x.equals(y) then
'they have the same ID
end if

Bind a detail report to a class model with properties stored in a Dictionary (VB.NET, Devexpress)

I'm trying the example from Devexpress
Dim binding As New XRBinding("Text", dsProducts1, "Products.UnitPrice")
But my models does not have their properties explicitly written in their class. It would take a method GetProperty("column_name_here") to get it's data. I'm wondering if the 3rd parameter of XRBinding can be a method? Like:
Dim binding As New XRBinding("Text", dsProducts1, product.GetProperty("name"))
Additional Info:
All of my model classes extends this Dao class which is responsible in getting data in the database. The Dao class have a protected variable as a Dictionary(Of String, Object) to store the values (key = column name, value = column row value) from the database.
Now when I want to get something in the database, I only call
Dim user As New User // this class extends the Dao class
Dim userId = user.GetProperty("id") // Method to get the value from Dictionary, first parameter is the Dictionary key or column name from the DB
I made this so that I wont have to create every model class and set the properties of that class, as it is kinda cumbersome.
It seems that there are no way to bind to some method. I suggest you to take a look at ExpandoObject dynamic class. The members of this class can be added at runtime. This class implements IDictionary(Of String, Object) interface which you can use to generate properties from your Dictionary(Of String, Object).
Here is example:
Example of base Dao class implementation with protected Dictionary(Of String, Object) property:
Public Class Dao
Private _values As Dictionary(Of String, Object)
Public Sub New()
_values = New Dictionary(Of String, Object)
End Sub
Public Overridable Sub Fill(index As Integer)
_values.Clear()
_values.Add("ID", index)
_values.Add("Product", "Banana " & index)
_values.Add("Price", 123.45 + index)
End Sub
Protected ReadOnly Property Values As Dictionary(Of String, Object)
Get
Return _values
End Get
End Property
End Class
Example of Dao class descendant with DynamicValues property which returns ExpandoObject based on Dictionary(Of String, Object) (you must omit the type of property):
Public Class DynamicDao
Inherits Dao
Private _dynamicValues As ExpandoObject
Public Overrides Sub Fill(index As Integer)
MyBase.Fill(index)
_dynamicValues = New ExpandoObject()
Dim keyValues = DirectCast(_dynamicValues, IDictionary(Of String, Object))
For Each pair In Values
keyValues.Add(New KeyValuePair(Of String, Object)(pair.Key, pair.Value))
Next
End Sub
Public ReadOnly Property DynamicValues ' <= There is no type. In hint is displayed «As Object».
Get
Return _dynamicValues
End Get
End Property
End Class
Usage of DynamicDao class in XtraReport:
Dim list = New List(Of DynamicDao)
For index% = 0 To 9
Dim dao = New DynamicDao()
dao.Fill(index%)
list.Add(dao)
Next
Dim labelID = New XRLabel()
labelID.DataBindings.Add(New XRBinding("Text", Nothing, "DynamicValues.ID"))
Dim labelProduct = New XRLabel()
labelProduct.DataBindings.Add(New XRBinding("Text", Nothing, "DynamicValues.Product"))
labelProduct.LeftF = 50
Dim labelPrice = New XRLabel()
labelPrice.DataBindings.Add(New XRBinding("Text", Nothing, "DynamicValues.Price"))
labelPrice.LeftF = 150
Dim detail = New DetailBand()
detail.Controls.Add(labelID)
detail.Controls.Add(labelProduct)
detail.Controls.Add(labelPrice)
Dim report = New XtraReport()
report.Bands.Add(detail)
report.DataSource = list
report.ShowRibbonPreview()

A class can only be a template for a single object not a template for a collection

I have a simple class List.vb which is the following:
Public Class List
Public fList As List(Of Integer)
Public Sub New()
fList = New List(Of Integer)
fList.Add(1)
fList.Add(2)
fList.Add(3)
fList.Add(4)
fList.Add(5)
End Sub
End Class
The Console application is using this class like the following:
Module Module1
Sub Main()
Dim fObject As List = New List
Dim cnt As Integer = 0
For Each x As Integer In fObject.fList
Console.WriteLine("hello; {0}", fObject.fList.Item(cnt).ToString())
cnt = cnt + 1
Next
Console.WriteLine("press [enter] to exit")
Console.Read()
End Sub
End Module
Can I change the class code so that List.vb is a list(of integer) type?
This would mean that in the Console code I could replace In fObject.fList with just In fObject?
Or am I barking up the wrong tree - should classes be single objects and lists should be collections of classes ?
Yes, you can do that. In order for an object to be compatible with For Each, it must have a GetEnumerator function:
Public Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return New IntListEnum(fList)
End Function
The IntListEnum class must, in turn, implement IEnumerator, like this:
Public Class IntListEnum Implements IEnumerator
Private listInt As List(Of Integer)
Dim position As Integer = -1
Public Sub New(ByVal fList As List(Of Integer))
listInt = fList
End Sub
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
position = position + 1
Return (position < listInt.Count)
End Function
Public Sub Reset() Implements IEnumerator.Reset
position = -1
End Sub
Public ReadOnly Property Current() As Object Implements IEnumerator.Current
Get
Try
Return listInt(position)
Catch ex As IndexOutOfRangeException
Throw New InvalidOperationException()
End Try
End Get
End Property
End Class
Now you can make fList private, and iterate your List as follows:
For Each x As Integer In fObject
You can see a complete example here.
The answer that dasblinkenlight has provided is excellent, but if all you need is a list that of integers that is pre-populated, you can just inherit from List(Of Integer) and then have the class populate itself in the constructor:
Public Class List
Inherits List(Of Integer)
Public Sub New()
Add(1)
Add(2)
Add(3)
Add(4)
Add(5)
End Sub
End Class
When you inherit from List(Of Integer), your class automatically gets all of the functionality implemented by that type, so your class also becomes a list class that works the same way. Then, you can just use it like this:
Dim fObject As New List()
For Each x As Integer In fObject
Console.WriteLine("hello; {0}", x)
Next

passing by reference in VB.Net

I posted a similar question before, which worked in C# (thanks to the community), but the actual problem was in VB.Net ( with option strict on). Problem is that tests are not passing.
Public Interface IEntity
Property Id() As Integer
End Interface
Public Class Container
Implements IEntity
Private _name As String
Private _id As Integer
Public Property Id() As Integer Implements IEntity.Id
Get
Return _id
End Get
Set(ByVal value As Integer)
_id = value
End Set
End Property
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
End Class
Public Class Command
Public Sub ApplyCommand(ByRef entity As IEntity)
Dim innerEntity As New Container With {.Name = "CommandContainer", .Id = 20}
entity = innerEntity
End Sub
End Class
<TestFixture()> _
Public Class DirectCastTest
<Test()> _
Public Sub Loosing_Value_On_DirectCast()
Dim entity As New Container With {.Name = "Container", .Id = 0}
Dim cmd As New Command
cmd.ApplyCommand(DirectCast(entity, IEntity))
Assert.AreEqual(entity.Id, 20)
Assert.AreEqual(entity.Name, "CommandContainer")
End Sub
End Class
The same is true in VB as in C#. By using the DirectCast, you're effectively creating a temporary local variable, which is then being passed by reference. That's an entirely separate local variable from the entity local variable.
This should work:
Public Sub Losing_Value_On_DirectCast()
Dim entity As New Container With {.Name = "Container", .Id = 0}
Dim cmd As New Command
Dim tmp As IEntity = entity
cmd.ApplyCommand(tmp)
entity = DirectCast(tmp, Container)
Assert.AreEqual(entity.Id, 20)
Assert.AreEqual(entity.Name, "CommandContainer")
End Sub
Of course it would be simpler just to make the function return the new entity as its return value...