VB.Net check for duplicate items in a collection base - vb.net

I have a class that inherits from CollectionBase. I tried to use the contains method to detect whether the Key already exists before inserting a new one. Here is what I have tried.
<Serializable()> Public Class validationList
Inherits CollectionBase
Public Function Add(ByVal Item As validationItem) As Integer
Return Me.List.Add(Item)
End Function
Default Public ReadOnly Property Item(ByVal index As Integer) As validationItem
Get
Return CType(List.Item(index), validationItem)
End Get
End Property
Public Sub Remove(ByVal index As Integer)
Me.List.RemoveAt(index)
End Sub
Protected Overrides Sub OnInsert(ByVal index As Integer, ByVal value As Object)
If Me.List.Contains(value) Then MsgBox("Already exist")
MyBase.OnInsert(index, value)
End Sub
Public Function IndexOf(ByVal key As validationItem)
Return List.IndexOf(key)
End Function
Public Sub AddRange(ByVal item() As validationItem)
For counter As Integer = 0 To item.GetLength(0) - 1
List.Add(item(counter))
Next
End Sub
End Class
<Serializable()> Public Class validationItem
Implements IEquatable(Of validationItem)
Private _key As validationTypes
Private _value As String
Public Sub New()
' Empty constructor is needed for serialization
End Sub
Public Sub New(ByVal k As validationTypes, ByVal v As String)
_key = k
_value = v
End Sub
Public Enum validationTypes
Madatory = 0
[Integer] = 1
Numeric = 2
[Decimal] = 3
MaxValue = 4
MinValue = 5
MinLength = 6
Email = 7
End Enum
Public Property Value As String
Get
Return _value
End Get
Set(ByVal Value As String)
_value = Value
End Set
End Property
Public Property Key As validationTypes
Get
Return _key
End Get
Set(ByVal value As validationTypes)
_key = value
End Set
End Property
Protected Overloads Function Equals(ByVal eqItem As validationItem) As Boolean Implements IEquatable(Of Testing_Project.validationItem).Equals
If eqItem Is Nothing Then Return False
Return Me._key = eqItem.Key
End Function
Public Overrides Function Equals(ByVal eqItem As Object) As Boolean
If eqItem Is Nothing Then Return False
Dim eqItemObj As validationItem = TryCast(eqItem, validationItem)
If eqItemObj Is Nothing Then Return False
Return Equals(eqItemObj)
End Function
Public Overrides Function GetHashCode() As Integer
Return Me._key.GetHashCode()
End Function
End Class
The validationList will be exposed from a usercontrol as a property, so that items could be added from the designer. When adding items I need to detect whether they already exist. I tried overriding the OnInsert but this sometime return that duplicates exists even when their aren't and doesn't report that duplicate exist when I try to add existing keys.

This indirectly answers the question after dealing with the issue which emerged in comments about Collection(Of T):
Add a reference to System.Collections.ObjectModel if needed, then
Imports System.Collections.ObjectModel
' a ValidationItem collection class
Public Class ValidationItems
Inherits Collection(Of ValidationItem)
Public Shadows Sub Add(NewItem As ValidationItem)
' test for existence
' do not add if it is not unique
Dim dupe As Boolean = False
For n As Int32 = 0 To Items.Count - 1
If Items(n).Key = NewItem.Key Then
dupe = True
Exit For
End If
Next
If dupe = False then
items.Add(newitem)
End if
' I would actually use an IndexOfKey function which might
' be useful elsewhere and only add if the return is -1
If IndexOfKey(NewItem.Key) <> -1 Then
Items.Add(newItem)
End If
End Sub
Some NET collection types implement Add as a function and return the item added. This sounds weird since you pass it the item to add. But returning Nothing if the item cannot be added is a neat semaphore for "I cant/wont do that". I cant recall if the std NET Collection Editor recognizes that or not.
One problem with using Contains is that it will test if item passed as param is the same object as one in the list. They never will be the same object, even if they have the same values. Testing the key in a loop is simpler than calling a method which implements an interface. (That previous answer was totally valid in the context presented, but the context has changed).
Even if you stay with CollectionBase, you want to handle it in the Add. If you try to remove it in OnInsert, VS will have problems deserializing the collection.
Also, your validationitem needs a Name property or the Collection Editor will display "Myassembly+MyType" as the Name (or a ToString override).
Other issues:
I am not sure your IndexOf will work. The list contains ValidationItems (objects), but you check it for _key (string). This will not matter if you change to Collection(Of T) which implements it for you.
The simple ctor is needed by the Collection Editor, not serialization. But the important thing is that it is there.
As for the comment about all Zeroes coming back - that is because your ValidationItem is not yet decorated for designer serialization. Maybe not the Collection Property either, that isnt shown.

Related

How can I edit null values in a BindingList when a DataGridView is bound to it?

I am still very new to data binding in Windows Forms with Visual Basic .NET, but trying to get familiar with it. I tried looking for information on this already, but to no avail.
I want to set up two-way binding between a DataGridView control and a list of objects (let's say they are of a made-up type called MyListElementClass), in a manner similar to what I saw in this answer to another question. Below is my implementation for MyListElementClass, in a file called MyListElementClass.vb:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
<Serializable>
Public NotInheritable Class MyListElementClass
Implements INotifyPropertyChanged
Implements IMyListElementClass
#Region "Fields"
Private _a As UShort
Private _b As Double
Private _c, _d, _e As Boolean
' End fields region.
#End Region
#Region "INotifyPropertyChanged implementation"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
' End INotifyPropertyChanged implementation region.
#End Region
#Region "IMyListElementClass implementation"
Public Property PropertyA As UShort Implements IMyListElementClass.PropertyA
Get
Return _a
End Get
Set(value As UShort)
If _a <> value Then
_a = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyB As Double Implements IMyListElementClass.PropertyB
Get
Return _b
End Get
Set(value As Double)
If _b <> value Then
_b = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyC As Boolean Implements IMyListElementClass.PropertyC
Get
Return _c
End Get
Set(value As Boolean)
If _c <> value Then
_c = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyD As Boolean Implements IMyListElementClass.PropertyD
Get
Return _d
End Get
Set(value As Boolean)
If _d <> value Then
_d = value
NotifyPropertyChanged()
End If
End Set
End Property
Public Property PropertyE As Boolean Implements IMyListElementClass.PropertyE
Get
Return _e
End Get
Set(value As Boolean)
If _e <> value Then
_e = value
NotifyPropertyChanged()
End If
End Set
End Property
' End IMyListElementClass implementation region.
#End Region
#Region "Constructors"
Public Sub New()
PropertyA = 0
PropertyB = 0
PropertyC = False
PropertyD = False
PropertyE = False
End Sub
Public Sub New(a As UShort, b As Double, c As Boolean, d As Boolean, e As Boolean)
Me.PropertyA = a
Me.PropertyB = b
Me.PropertyC = c
Me.PropertyD = d
Me.PropertyE = e
End Sub
Public Sub New(other As IMyListElementClass)
If other Is Nothing Then Throw New ArgumentNullException(NameOf(other))
CopyFrom(other)
End Sub
' End constructors region.
#End Region
#Region "Methods"
Public Sub CopyFrom(other As IMyListElementClass)
If other Is Nothing Then Throw New ArgumentNullException(NameOf(other))
With other
PropertyA = .PropertyA
PropertyB = .PropertyB
PropertyC = .PropertyC
PropertyD = .PropertyD
PropertyE = .PropertyE
End With
End Sub
' End methods region.
#End Region
End Class
The idea here is that the DataGridView control will show a list of available "slots" (rows) that instances of MyListElementClass can be entered into. However, some of these slots could be empty, and may need to be filled in or cleared later. The number of rows in the table is specified by a number entered elsewhere, so the user cannot add or remove rows on the fly; They have to work with the space that's given.
My current attempt at this is to have the DataGridView control bound to a BindingList(Of MyListElementClass), where its size is always equal to the number of available slots and empty slots are represented by null elements. However, I found that if I have these null values present in the BindingList(Of MyListElementClass), these rows cannot be edited by the user in the DataGridView control which is bound to it, and I'm not really sure how to handle this.
An example of what I'm trying to do in my user control which contains the DataGridView (named dgvDataGridView here and with columns already set up through the designer):
Public Class MyUserControl
Private _myBindingList As BindingList(Of MyListElementClass)
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
dgvDataGridView.AutoGenerateColumns = False ' Columns already created through the Visual Studio designer with the ordering and header text I want.
SetUpTableDataBinding()
End Sub
Private Sub SetUpTableDataBinding()
colA.DataPropertyName = NameOf(MyListElementClass.PropertyA)
colB.DataPropertyName = NameOf(MyListElementClass.PropertyB)
colC.DataPropertyName = NameOf(MyListElementClass.PropertyC)
colD.DataPropertyName = NameOf(MyListElementClass.PropertyD)
colE.DataPropertyName = NameOf(MyListElementClass.PropertyE)
Dim initialList As New List(Of MyListElementClass)(Enumerable.Repeat(Of MyListElementClass)(Nothing, 1)) ' First row will contain a null value, and hence be "empty".
_myBindingList = New BindingList(Of MyListElementClass)(initialList)
Dim source = New BindingSource(_myBindingList, Nothing)
dgvDataGridView.DataSource = source
' Some test data for data binding.
_myBindingList.AddNew() ' Adds a new MyListElementClass instance with default property values.
_myBindingList.Add(New MyListElementClass(2345, 7.4, False, True, False)) ' Just some sample values.
End Sub
End Class
After this user control loads, I can see an empty row, a row with default values for the MyListElementClass, and a row with some sample values appear, for three rows total. I can edit the second and third rows, but not the first (any values I enter immediately vanish).
Again, in completely unfamiliar territory here, so bear with me. If I cannot get this to work, then I will abandon this idea and return to manually setting and retrieving data in the DataGridView cells like I've always done up until now.
Null values cannot be edited, replace them with empty strings instead and I think you will find that it will work as intended.

VB .Net - ShouldSerialize function

I have a problem with ShouldSerialize function.
I defined an usercontrol with a label (named Label1) on it.
Now i put the code below in the usercontrol class :
Imports System.ComponentModel
Public Class UserControl1
Dim _Range As UShort = 100
Private Sub UserControl1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Label1.Text = _Range
End Sub
Public Function ShouldSerializeTBValueRange() As Boolean
Return _Range <> 200
End Function
Public Sub ResetTBValueRange()
_Range = 200
Label1.Text = _Range
End Sub
Public Property TBValueRange As UShort
Get
Return _Range
End Get
Set(ByVal Steps As UShort)
_Range = Steps
Label1.Text = _Range
End Set
End Property
End Class
Now in a new form include the usercontrol.
In the properties grid of the usercontrol u can find the property TBValueRange.
If you right click in the property name you can Reinit the property.
After reinit u can see value 200 in the property.
Now, regenerate the project will reset the property to the initial value (id 100).
Why the value 200 didn't stay?
If i replace the line Return _Range <> 200 in the function ShouldSerializeTBValueRange() by
Return Not _Range.Equals(200)
it will work.
i don't understand.
Anyone could explain this?
The methods are working as coded. The thing which seems to not work as expected is the Not _Range.Equals(200) portion.
You initialize _Range to 100: Dim _Range As UShort = 100
Then, your ShouldSerializeTBValueRange method tells VS to only save the range value when it is not 200: Return _Range <> 200
So, when you reset it to 200 in the IDE, it wont save a value of 200, and the initial value of 100 displays.
The code uses one value for the default, 100 but use a different value for ShouldSerialize test. You should either change it to use a default of 200:
Dim _Range As UShort = 200
Or change the ShouldSerialize test to use 100:
Public Function ShouldSerializeTBValueRange() As Boolean
Return _Range <> 100
End Function
Treating the default value as the default, and using only one value for the default everything works as expected using just ShouldSerializexxx and Resetxxx with no need for anything else.
' THIS is the default value -
' ie the value that need not be saved because the
' control automatically starts with that value
Dim _Range As UShort = 200US
' Controls:
' - displays the prop value in Bold in the property window
' when the value is NOT the default
' - saves the value when it is NOT the default
' - enables/disables the RESET function
Public Function ShouldSerializeTBValueRange() As Boolean
Return (_Range <> 200US)
End Function
Using one value for the actual default (100) and then returning T/F based on a different value (200) results in
100 being saved when it need not be
200 not being saved when it should be
the Reset menu item and Bold value being in the incorrect state
The oddity is that Not _Range.Equals(200) seems to fail. It returns False after a reset (to 200) which causes the value to be saved when it really should not. There are 2 overloads for this:
UInt16.Equals(obj As Object) As Boolean
UInt16.Equals(v As UShort) As Boolean
If you pass anything other than an actual UInt16/UShort, the value will be boxed as Object. So, Not _Range.Equals(200) is using the first because 200 is Int32. The ILCode for that version is:
public override bool Equals(object obj)
{
return obj is ushort && this == (ushort)obj;
}
The test will fail the first test because obj contains an Int32. If you pass a UShort, it will work:
UShort.Equals(_Range, 200US)
'or:
_Range.Equals(200US)
All good reasons to understand the different data types, avoid boxing (As Object) and always and forever set Option Strict On.
Thanks you all to have spent time on my question.
I found my answer : when ShouldSerialize function return false the property value isn't save in the file Form1.designer.vb, so it gets the original value.
For my needed the ShouldSerialize function should always return true.
Public Function ShouldSerializeTBValueRange() As Boolean
Return true
End Function
So the property value will always be saved.
Now, what i'm looking for is how to control the context menu of the property grid.
I want the option "Reinit" greyed when the value is good.
Finally, i found something, with the use of PropertyDescriptor.
For whose are interested, we can save properties in the designer.vb file and test the value to reset.
here is my program, based on the article from MSDN - Microsft:
Imports System.ComponentModel
Imports System.Windows.Forms.Design
<Designer(GetType(DemoControlDesigner))>
Public Class UserControl1
Dim _MyProperty1 As String = "Test1"
Dim _MyProperty2 As Integer = 100
Dim _MyProperty3 As UShort = 200
Public Sub New()
InitializeComponent()
End Sub
Public Function CanResetMyProperty1() As Boolean
Return _MyProperty1 <> "Test"
End Function
Public Sub ResetMyProperty1()
_MyProperty1 = "Test"
End Sub
Public Property MyProperty1 As String
Get
Return _MyProperty1
End Get
Set(ByVal Value As String)
_MyProperty1 = Value
End Set
End Property
Public Function CanResetMyProperty2() As Boolean
Return _MyProperty2 <> 150
End Function
Public Sub ResetMyProperty2()
_MyProperty2 = 150
End Sub
Public Property MyProperty2 As Integer
Get
Return _MyProperty2
End Get
Set(ByVal Value As Integer)
_MyProperty2 = Value
End Set
End Property
Public Function CanResetMyProperty3() As Boolean
Return _MyProperty3 <> _MyProperty2
End Function
Public Sub ResetMyProperty3()
_MyProperty3 = _MyProperty2
End Sub
Public Property MyProperty3 As UShort
Get
Return _MyProperty3
End Get
Set(ByVal Value As UShort)
_MyProperty3 = Value
End Set
End Property
End Class
Friend NotInheritable Class SerializePropertyDescriptor
Inherits PropertyDescriptor
Private _pd As PropertyDescriptor = Nothing
Public Sub New(ByVal pd As PropertyDescriptor)
MyBase.New(pd)
_pd = pd
End Sub
Public Overrides ReadOnly Property Attributes() As AttributeCollection
Get
Return Me._pd.Attributes
End Get
End Property
Protected Overrides Sub FillAttributes(ByVal attributeList As IList)
MyBase.FillAttributes(attributeList)
End Sub
Public Overrides ReadOnly Property ComponentType() As Type
Get
Return Me._pd.ComponentType
End Get
End Property
Public Overrides ReadOnly Property Converter() As TypeConverter
Get
Return Me._pd.Converter
End Get
End Property
Public Overrides Function GetEditor(ByVal editorBaseType As Type) As Object
Return Me._pd.GetEditor(editorBaseType)
End Function
Public Overrides ReadOnly Property IsReadOnly() As Boolean
Get
Return Me._pd.IsReadOnly
End Get
End Property
Public Overrides ReadOnly Property PropertyType() As Type
Get
Return Me._pd.PropertyType
End Get
End Property
Public Overrides Function CanResetValue(ByVal component As Object) As Boolean
Try
Return CallByName(component, "CanReset" & _pd.Name, CallType.Get, Nothing)
Catch ex As Exception
MsgBox(ex.Message)
End Try
Return False
End Function
Public Overrides Function GetValue(ByVal component As Object) As Object
Return Me._pd.GetValue(component)
End Function
Public Overrides Sub ResetValue(ByVal component As Object)
Me._pd.ResetValue(component)
End Sub
Public Overrides Sub SetValue(ByVal component As Object, ByVal val As Object)
Me._pd.SetValue(component, val)
End Sub
Public Overrides Function ShouldSerializeValue(ByVal component As Object) As Boolean
Return True
End Function
End Class
Class DemoControlDesigner
Inherits ControlDesigner
Dim PropertiesToSerialize As String() = {"MyProperty1", "MyProperty2", "MyProperty3"}
Protected Overrides Sub PostFilterProperties(ByVal properties As IDictionary)
Dim original As PropertyDescriptor
For Each PropName As String In PropertiesToSerialize
If properties.Contains(PropName) Then
original = properties(PropName)
properties(PropName) = New SerializePropertyDescriptor(original)
End If
Next
MyBase.PostFilterProperties(properties)
End Sub
End Class
For each property that should be serialized and tested for reset, we should write a sub "CanResetPropertyName" which test the value to reset (see sample).
And the values stay now, even if we regenerate the project.
It's working fine for me, maybe it can be improved.
Regards.

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

VB.Net check for existing keys in a collection

I have a class that inherits from the CollectionBase and when adding items I want to detect whether the collection already contains the key that is going to be inserted. If it does I want to send a warning via a MsgBox(). Here is the code & what I've tried
<Serializable()> Public Class validationList
Inherits CollectionBase
Public Function Add(ByVal Item As validationItem) As Integer
MsgBox(Me.List.Contains(Item))
Return Me.List.Add(Item)
End Function
Default Public ReadOnly Property Item(ByVal index As Integer) As validationItem
Get
Return CType(List.Item(index), validationItem)
End Get
End Property
Public Sub Remove(ByVal index As Integer)
Me.List.RemoveAt(index)
End Sub
Public Function IndexOf(ByVal key As validationItem)
Return List.IndexOf(key)
End Function
Public Sub AddRange(ByVal item() As validationItem)
For counter As Integer = 0 To item.GetLength(0) - 1
List.Add(item(counter))
Next
End Sub
End Class
<Serializable()> Public Class validationItem
Private _key As validationTypes
Private _value As String
Public Enum validationTypes
man = 0
num = 1
End Enum
Public Property Value As String
Get
Return _value
End Get
Set(ByVal Value As String)
_value = Value
End Set
End Property
Public Property Key As validationTypes
Get
Return _key
End Get
Set(ByVal value As validationTypes)
_key = value
End Set
End Property
Public Sub New()
' Empty constructor is needed for serialization
End Sub
Public Sub New(ByVal k As validationTypes, ByVal v As String)
_key = k
_value = v
End Sub
End Class
Public Class textbox
Inherits System.Windows.Forms.TextBox
Private _validation As New validationList
<System.ComponentModel.DesignerSerializationVisibility(Content)>
Public Property validation As validationList
Get
Return _validation
End Get
Set(ByVal value As validationList)
_validation = value
End Set
End Property
End Class
In the add method I tried to check whether the collection already has this item. But it always returns -1.
Here is code that adds a new item to the collection
Textbox1.validation.Add(New validationItem With {.Key = validationItem.validationTypes.man, .Value = "1"})
To make Contains work, you'll have to implement Equals/GetHashCode on validationItem or implement the IEquatable(Of T) interface:
This method determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable(Of T).Equals method for T (the type of values in the list).
Here's an example implementation for Equals/GetHashCode that checks both, Key and Value:
<Serializable> _
Public Class validationItem
Protected Overloads Function Equals(other As validationItem) As Boolean
Return _value = other._value AndAlso _key = other._key
End Function
Public Overrides Function Equals(obj As Object) As Boolean
If obj Is Nothing Then
Return False
End If
If Me Is obj Then
Return True
End If
If obj.GetType() IsNot Me.GetType() Then
Return False
End If
Return Equals(DirectCast(obj, validationItem))
End Function
Public Overrides Function GetHashCode() As Integer
Return ((If(_value IsNot Nothing, _value.GetHashCode(), 0)) * 397) Xor CInt(_key)
End Function
...
End Class
You could also use use LINQ, here's an example that only checks for Key:
Public Function Add(ByVal Item As validationItem) As Integer
If Me.List.OfType(Of validationItem).Any(Function(i) i.Key = Item.Key) Then
' Do something '
Else
Return Me.List.Add(Item)
End If
End Function
You need to just check for whether the key exist or not and only show it if it exists:
Public Function Add(ByVal Item As validationItem) As Integer
If Me.List.Contains(Item) Then MsgBox("The key already exists")
Return Me.List.Add(Item)
End Function
As it stands you are just returning the result of the Contains method which is a Boolean (hence the -1)

Create a List property which cannot be changed externally

I have a public class in my VB.NET project which has a List(Of String) property. This list needs to be modified by other classes within the project, but since the class may (at some time in the future) be exposed outside the project, I want it to be unmodifiable at that level. The modification of the existing property within the project will only be done by calling the list's methods (notably .Add, occasionally .Clear), not by a wholesale replacement of the property value with a new List (which is why I have it as a ReadOnly property).
I have come up with a way of doing it, but I'm not sure that it's exactly what you would call "elegant".
It's this:
Friend mlst_ParameterNames As List(Of String) = New List(Of String)
Public ReadOnly Property ParameterNames() As List(Of String)
Get
Return New List(Of String)(mlst_ParameterNames)
End Get
End Property
Now this just works fine and dandy. Any class in the project which accesses the mlst_ParameterNames field directly can modify it as needed, but any procedures which access it through the public property can bang away at modifying it to their heart's content, but will get nowhere since the property procedure is always returning a copy of the list, not the list itself.
But, of course, that carries overhead which is why I feel that it's just... well, viscerally "wrong" at some level, even though it works.
The parameters list will never be huge. At most it will only contain 50 items, but more commonly less than ten items, so I can't see this ever being a performance killer. However it has of course set me to thinking that someone, with far more VB.NET hours under their belt, may have a much neater and cleaner idea.
Anyone?
Instead of creating a new copy of the original list, you should use the AsReadOnly method to get a read-only version of the list, like this:
Friend mlst_ParameterNames As List(Of String) = New List(Of String)
Public ReadOnly Property ParameterNames() As ReadOnlyCollection(Of String)
Get
Return mlst_ParameterNames.AsReadOnly()
End Get
End Property
According to the MSDN:
This method is an O(1) operation.
Which means that the speed of the AsReadOnly method is the same, regardless of the size of the list.
In addition to the potential performance benefits, the read-only version of the list is automatically kept in sync with the original list, so if consuming code keeps a reference to it, its referenced list will still be up-to-date, even if items are later added to or removed from the list.
Also, the list is truly read-only. It does not have an Add or Clear method, so there will be less confusion for others using the object.
Alternatively, if all you need is for consumers to be able to iterate through the list, then you could just expose the property as IEnumerable(Of String) which is, inherently, a read-only interface:
Public ReadOnly Property ParameterNames() As IEnumerable(Of String)
Get
Return mlst_ParameterNames
End Get
End Property
However, that makes it only useful to access the list in a For Each loop. You couldn't, for instance, get the Count or access the items in the list by index.
As a side note, I would recommend adding a second Friend property rather than simply exposing the field, itself, as a Friend. For instance:
Private _parameterNames As New List(Of String)()
Public ReadOnly Property ParameterNames() As ReadOnlyCollection(Of String)
Get
Return _parameterNames.AsReadOnly()
End Get
End Property
Friend ReadOnly Property WritableParameterNames() As List(Of String)
Get
Return _parameterNames
End Get
End Property
What about providing a Locked property that you can set, each other property then checks this to see if it's been locked...
Private m_Locked As Boolean = False
Private mlst_ParameterNames As List(Of String) = New List(Of String)
Public Property ParameterNames() As List(Of String)
Get
Return New List(Of String)(mlst_ParameterNames)
End Get
Set(value As List(Of String))
If Not Locked Then
mlst_ParameterNames = value
Else
'Whatever action you like here...
End If
End Set
End Property
Public Property Locked() As Boolean
Get
Return m_Locked
End Get
Set(value As Boolean)
m_Locked = value
End Set
End Property
-- EDIT --
Just to add to this, then, here's a basic collection...
''' <summary>
''' Provides a convenient collection base for search fields.
''' </summary>
''' <remarks></remarks>
Public Class SearchFieldList
Implements ICollection(Of String)
#Region "Fields..."
Private _Items() As String
Private _Chunk As Int32 = 16
Private _Locked As Boolean = False
'I've added this in so you can decide if you want to fail on an attempted set or not...
Private _ExceptionOnSet As Boolean = False
Private ptr As Int32 = -1
Private cur As Int32 = -1
#End Region
#Region "Properties..."
Public Property Items(ByVal index As Int32) As String
Get
'Make sure we're within the index bounds...
If index < 0 OrElse index > ptr Then
Throw New IndexOutOfRangeException("Values between 0 and " & ptr & ".")
Else
Return _Items(index)
End If
End Get
Set(ByVal value As String)
'Make sure we're within the index bounds...
If index >= 0 AndAlso Not _Locked AndAlso index <= ptr Then
_Items(index) = value
ElseIf _ExceptionOnSet Then
Throw New IndexOutOfRangeException("Values between 0 and " & ptr & ". Use Add() or AddRange() method to append fields to the collection.")
End If
End Set
End Property
Friend Property ChunkSize() As Int32
Get
Return _Chunk
End Get
Set(ByVal value As Int32)
_Chunk = value
End Set
End Property
Public ReadOnly Property Count() As Integer Implements System.Collections.Generic.ICollection(Of String).Count
Get
Return ptr + 1
End Get
End Property
''' <summary>
''' Technically unnecessary, just kept to provide coverage for ICollection interface.
''' </summary>
''' <returns>Always returns false</returns>
''' <remarks></remarks>
Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.Generic.ICollection(Of String).IsReadOnly
Get
Return False
End Get
End Property
#End Region
#Region "Methods..."
Public Shadows Sub Add(ByVal pItem As String) Implements System.Collections.Generic.ICollection(Of String).Add
If Not _Items Is Nothing AndAlso _Items.Contains(pItem) Then Throw New InvalidOperationException("Field already exists.")
ptr += 1
If Not _Items Is Nothing AndAlso ptr > _Items.GetUpperBound(0) Then SetSize()
_Items(ptr) = pItem
End Sub
Public Shadows Sub AddRange(ByVal collection As IEnumerable(Of String))
Dim cc As Int32 = collection.Count - 1
For sf As Int32 = 0 To cc
If _Items.Contains(collection.ElementAt(sf)) Then
Throw New InvalidOperationException("Field already exists [" & collection.ElementAt(sf) & "]")
Else
Add(collection.ElementAt(sf))
End If
Next
End Sub
Public Function Remove(ByVal item As String) As Boolean Implements System.Collections.Generic.ICollection(Of String).Remove
Dim ic As Int32 = Array.IndexOf(_Items, item)
For lc As Int32 = ic To ptr - 1
_Items(lc) = _Items(lc + 1)
Next lc
ptr -= 1
End Function
Public Sub Clear() Implements System.Collections.Generic.ICollection(Of String).Clear
ptr = -1
End Sub
Public Function Contains(ByVal item As String) As Boolean Implements System.Collections.Generic.ICollection(Of String).Contains
Return _Items.Contains(item)
End Function
Public Sub CopyTo(ByVal array() As String, ByVal arrayIndex As Integer) Implements System.Collections.Generic.ICollection(Of String).CopyTo
_Items.CopyTo(array, arrayIndex)
End Sub
#End Region
#Region "Private..."
Private Sub SetSize()
If ptr = -1 Then
ReDim _Items(_Chunk)
Else
ReDim Preserve _Items(_Items.GetUpperBound(0) + _Chunk)
End If
End Sub
Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of String) Implements System.Collections.Generic.IEnumerable(Of String).GetEnumerator
Return New GenericEnumerator(Of String)(_Items, ptr)
End Function
Private Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
#End Region
End Class
Friend Class GenericEnumerator(Of T)
Implements IEnumerator(Of T)
#Region "fields..."
Dim flist() As T
Dim ptr As Int32 = -1
Dim size As Int32 = -1
#End Region
#Region "Properties..."
Public ReadOnly Property Current() As T Implements System.Collections.Generic.IEnumerator(Of T).Current
Get
If ptr > -1 AndAlso ptr < size Then
Return flist(ptr)
Else
Throw New IndexOutOfRangeException("=" & ptr.ToString())
End If
End Get
End Property
Public ReadOnly Property Current1() As Object Implements System.Collections.IEnumerator.Current
Get
Return Current
End Get
End Property
#End Region
#Region "Constructors..."
Public Sub New(ByVal fieldList() As T, Optional ByVal top As Int32 = -1)
flist = fieldList
If top = -1 Then
size = fieldList.GetUpperBound(0)
ElseIf top > -1 Then
size = top
Else
Throw New ArgumentOutOfRangeException("Expected integer 0 or above.")
End If
End Sub
#End Region
#Region "Methods..."
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
ptr += 1
Return ptr <= size
End Function
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
ptr = -1
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class