Populating a combo box with a list of functions - Need Advice - vb.net

I'm looking for some advice on the best way to handle this.
I have a list of about 200 "Functions" which are listed in a combo box. When the user selects a 'function' from the list, I need to return the functionID (integer).
I know this can be done easily by binding a dataset to the key and value of the combobox, I'm just not sure about the best way to populate the dataset.
I feel that the way I'm doing it currently is very convoluted:
I currently have a txt file as an embedded resource which I write to a temporary directory, then I use the following code to read in that text file and populate that box by setting the combobox's datasource and Display Member. It does this by way of a custom class which is implementing System.Collections.IList.
I have pasted the code below. The reason I want to simplify it is that I dislike writing the text file to the disk, because sometimes it fails.
I'm looking for a way to populate my combobox and return my ID, without writing anything to the user's temp folder.
I am open to changing the format of the embedded resource, and or the code.
The fnlist.txt is formatted currently as follows.
index, Function Name, ID
The index is only included for sorting (to keep NONE at the bottom, and unknown function at the top), and I suppose is not strictly required.
#Region "Function lookup"
Dim path As String = System.IO.Path.GetTempPath
Dim _objFnXtef As New clsFunctionXref(path & "fnList.txt")
Private Sub populate_list()
functionlist.DataSource = _objFnXtef
functionlist.DisplayMember = "StrFunction"
End Sub 'Populates the function list
Function get_index(ByVal fnid As Integer)
Dim iLookupNumber As Integer = fnid
Dim tmpFnInfo As New clsFunctionInfo
Dim iReturnIdx As Integer = -1
If iLookupNumber <> 0 Then
tmpFnInfo.IFunctionNumber = iLookupNumber
iReturnIdx = _objFnXtef.IndexOf(tmpFnInfo)
If iReturnIdx <> -1 Then
Return iReturnIdx - 1
Else
Return get_index(9999)
End If
End If
Return 0
End Function 'Returns index of specified function number
#End Region 'All function list functions
Here is the code when a user changes the drop down:
Private Sub functionlist_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles functionlist.SelectedIndexChanged
Dim iReturnFuctionID As Integer = 0
Dim tmpFnInfo As New clsFunctionInfo
tmpFnInfo = _objFnXtef(functionlist.SelectedIndex)
iReturnFuctionID = tmpFnInfo.IFunctionNumber
Func = (iReturnFuctionID)
End Sub
And here is the supporting class:
Imports Microsoft.VisualBasic.FileIO
Public Class clsFunctionInfo
Private _idxFunction As Integer
Public Property IdxFunction() As Integer
Get
Return _idxFunction
End Get
Set(ByVal value As Integer)
_idxFunction = value
End Set
End Property
Private _strFunction As String
Public Property StrFunction() As String
Get
Return _strFunction
End Get
Set(ByVal value As String)
_strFunction = value
End Set
End Property
Private _iFunctionNumber As Integer
Public Property IFunctionNumber() As Integer
Get
Return _iFunctionNumber
End Get
Set(ByVal value As Integer)
_iFunctionNumber = value
End Set
End Property
End Class
Public Class clsFunctionXref
Implements System.Collections.IList
Private _colFunctionInfo As New Collection
Private _filePath As String
Public Property FilePath() As String
Get
Return _filePath
End Get
Set(ByVal value As String)
_filePath = value
End Set
End Property
Public Sub New(ByVal filename As String)
_filePath = filename
Dim _idx As Integer = 1
Dim fields As String()
Dim delimiter As String = ","
Dim iFnx As Integer
Using parser As New TextFieldParser(filename)
parser.SetDelimiters(delimiter)
While Not parser.EndOfData
' Read in the fields for the current line
fields = parser.ReadFields()
Try
iFnx = Convert.ToInt16(fields(0).ToString)
Catch ex As Exception
MessageBox.Show("Error reading file. " & ex.ToString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
Exit Sub
End Try
Dim objFunction As New clsFunctionInfo
objFunction.IdxFunction = _idx
objFunction.IFunctionNumber = iFnx
objFunction.StrFunction = fields(1).ToString
Me.Add(objFunction)
_idx += 1
End While
End Using
End Sub
Public Function Add(ByVal value As Object) As Integer Implements System.Collections.IList.Add
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
SyncLock Me.SyncRoot
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End SyncLock
ReIndex()
End If
SyncLock Me.SyncRoot
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString)
End SyncLock
End Function
Public Sub Clear() Implements System.Collections.IList.Clear
SyncLock Me.SyncRoot
_colFunctionInfo.Clear()
End SyncLock
End Sub
Public Function Contains(ByVal value As Object) As Boolean Implements System.Collections.IList.Contains
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
Return True
Else
Return False
End If
End Function
Public ReadOnly Property Count() As Integer Implements System.Collections.ICollection.Count
Get
Return _colFunctionInfo.Count
End Get
End Property
Public ReadOnly Property IsReadOnly() As Boolean Implements System.Collections.IList.IsReadOnly
Get
Return False
End Get
End Property
Public Sub Remove(ByVal value As Object) Implements System.Collections.IList.Remove
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
SyncLock Me.SyncRoot
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End SyncLock
ReIndex()
End If
End Sub
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return _colFunctionInfo.GetEnumerator
End Function
Public Sub Insert(ByVal index As Integer, ByVal value As Object) Implements System.Collections.IList.Insert
SyncLock Me.SyncRoot
If _colFunctionInfo.Contains(value.IFunctionNumber.ToString) Then
_colFunctionInfo.Remove(value.IFunctionNumber.ToString)
End If
If index < _colFunctionInfo.Count Then
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString, index - 1)
Else
_colFunctionInfo.Add(value, value.IFunctionNumber.ToString)
End If
End SyncLock
ReIndex()
End Sub
Public Sub RemoveAt(ByVal index As Integer) Implements System.Collections.IList.RemoveAt
SyncLock Me.SyncRoot
If _colFunctionInfo.Count <= index And index > 0 Then
_colFunctionInfo.Remove(index)
End If
End SyncLock
ReIndex()
End Sub
Private Sub ReIndex()
SyncLock Me.SyncRoot
Dim iReIndex As Integer = 1
Dim colTemp As New Collection
For Each obj As clsFunctionInfo In _colFunctionInfo
obj.IdxFunction = iReIndex
colTemp.Add(obj, obj.IFunctionNumber)
iReIndex += 1
Next
_colFunctionInfo.Clear()
For Each obj1 As clsFunctionInfo In colTemp
_colFunctionInfo.Add(obj1, obj1.IFunctionNumber.ToString)
Next
colTemp.Clear()
End SyncLock
End Sub
Public ReadOnly Property IsSynchronized() As Boolean Implements System.Collections.ICollection.IsSynchronized
Get
Return True
End Get
End Property
Public ReadOnly Property SyncRoot() As Object Implements System.Collections.ICollection.SyncRoot
Get
Dim _syncRoot As New Object
Return _syncRoot
End Get
End Property
Public ReadOnly Property IsFixedSize() As Boolean Implements System.Collections.IList.IsFixedSize
Get
Return False
End Get
End Property
Public Sub CopyTo(ByVal array As System.Array, ByVal index As Integer) Implements System.Collections.ICollection.CopyTo
For Each obj As clsFunctionInfo In _colFunctionInfo
array(index) = obj
index += 1
Next
End Sub
Public Function IndexOf(ByVal value As Object) As Integer Implements System.Collections.IList.IndexOf
SyncLock Me.SyncRoot
Dim tmpFnInfo As New clsFunctionInfo
Dim tmpFunctionNumber As Integer
Dim tmpidx As Integer = -1
tmpFnInfo = DirectCast(value, clsFunctionInfo)
tmpFunctionNumber = tmpFnInfo.IFunctionNumber
For Each obj In _colFunctionInfo
tmpFnInfo = DirectCast(obj, clsFunctionInfo)
If tmpFunctionNumber = tmpFnInfo.IFunctionNumber Then
tmpidx = tmpFnInfo.IdxFunction
Exit For
End If
Next
Return tmpidx
End SyncLock
End Function
Default Public Property Item(ByVal index As Integer) As Object Implements System.Collections.IList.Item
Get
index += 1
Return _colFunctionInfo(index)
End Get
Set(ByVal value As Object)
End Set
End Property
End Class
I'm sorry that this is so long, but I know that someone on here has some great suggestions on how to handle this because I'm having a little trouble wrapping my head around it. I think I've been starring at it too long.

since you have the text file as an embedded resource, you can open a stream to the file from there, without having to write it to disk. The ResourceReader class should help you.

Related

How can I use textbox textchanged events to change user input classes?

InputValue1,..OperationAdd are instances of the class InputNumber. How do I assign my Inputvalues to the corresponding textboxes as option Strict On is activated?
Class MainWindow
Dim InputValue1 As New InputNumber
Dim InputValue2 As New InputNumber
Dim ExpectedResultValue As New InputNumber
Dim OperationAdd As New InputNumber
Private Sub TBoxNumber1_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TBoxNumber1.TextChanged
TBoxNumber1.Text = InputValue1 'There is an error in this line, value of type InputNumber 'cannot be converted to a string
End Sub
Below is the InputNumber Class:
Public Class InputNumber
Inherits Input
Private _number As Integer
Public Property Number As Integer
Get
Return _number
End Get
Set(value As Integer)
_number = value
End Set
End Property
Protected Overrides Function Validate(s As String) As Boolean
Dim isValid As Boolean = Integer.TryParse(s, _number)
Return isValid
End Function
End Class
It seems to me that you should be getting the input from the TextBox into your InputNumber object, not the other way around. This:
TBoxNumber1.Text = InputValue1
should probably be this:
InputValue1.Validate(TBoxNumber1.Text)
When it actually comes time to display the value of that InputNumber, you would have to use InputValue1.Number.ToString(). Personally, I would add this to your InputNumber class:
Public Overrides Function ToString() As String
Return Number.ToString()
End Function
and then you can use InputValue1.ToString() instead.
EDIT:
It's been pointed out that the Validate method is Protected so it can't be called like that. Unless there's some use for it in the Input base class, I'm not sure what it's for because, as it stands, you'd need to validate externally anyway, e.g.
Dim number As Integer
If Integer.TryParse(TBoxNumber1.Text, number) Then
InputValue1.Number = number
End If
I am questioning the implementation of InputNumber. It does not make much sense in the current form. We don't know when validation is happen and Number is integer in fact.
I would do something like this.
Public Class InputNumber
Private _number As Integer
Private _hasNumber As Boolean
Public Sub New(s As String)
SetNumber(s)
End Sub
Public Sub New()
End Sub
Public ReadOnly Property Number As Integer
Get
Return _number
End Get
End Property
Public ReadOnly Property HasNumber As Boolean
Get
Return _hasNumber
End Get
End Property
Public Sub SetNumber(s As String)
_hasNumber = Integer.TryParse(s, _number)
End Sub
' For consistency...
Public Sub SetNumber(i As Integer)
_hasNumber = true
_number = i
End Sub
Public Overrides Function ToString() As String
If HasNumber Then Return Number.ToString()
Return String.Empty
End Function
End Class
Then your usage will be (and I agree with #jmcilhinney, when you have TextChange event, you want to take a value from the text box and set a variable)
Dim inp As New InputNumber("ddd")
txtBox1.Text = If(inp.HasNumber, inp.Number.ToString(), "NO VALUE") '' example to use HasNumber
inp.SetNumber("sss")
txtBox2.Text = inp.ToString() '' Example to use straight value
inp.SetNumber(10)
Dim current As Integer = If(inp.HasNumber, inp.Number, -1) '' using as numeric value
Now, this class makes slightly more sense

System.ArgumentException: At least one object must implement IComparable

In my MergeCollection class i have overide InsertItem method to check for specific case. Nevertheless when it comes to the line Items.Any(.. it throws me exception as below.
System.ArgumentException: 'At least one object must implement IComparable.'
Merge class:
Public Class Merge
Property Min As Integer
Property Max As Integer?
Property Value As Double
Public Sub New(min As Integer, max As Integer?, value As Integer)
Me.Min = min
Me.Max = max
Me.Value = value
End Sub
End Class
Public Enum SortCriteria
MinThenMax
MaxThenMin
End Enum
Some comparer:
Public Class MergeComparer
Implements IComparer(Of Merge)
Public SortBy As SortCriteria = SortCriteria.MinThenMax
Public Function Compare(x As Merge, y As Merge) As Integer Implements IComparer(Of Merge).Compare
'to be implemented
End Function
End Class
Collection class:
Public Class MergeCollection
Inherits Collection(Of Merge)
Public SortBy As SortCriteria = SortCriteria.MinThenMax
Protected Overrides Sub InsertItem(index As Integer, item As Merge)
if IsNothing(item.Max)
If Items.Any(Function(myObject) IsNothing(Items.Max)) Then
Return
End If
End If
MyBase.InsertItem(index, item)
End Sub
Public Sub Sort()
Dim allItems = Items.ToArray()
Array.Sort(allItems)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparison As Comparison(Of Merge))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparison)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
Public Sub Sort(comparer As IComparer(Of Merge))
Dim allItems = Items.ToArray()
Array.Sort(allItems, comparer)
For i = 0 To allItems.GetUpperBound(0)
Items(i) = allItems(i)
Next
End Sub
End Class
The code asks for Items.Max. To find the Max value, it has to compare the Items to each other.
Public Class Merge
Implements IComparable(Of Merge)
Property Min As Integer
Property Max As Integer?
Property Value As Double
Public Sub New()
' empty constructor
End Sub
Public Sub New(min As Integer, max As Integer?, value As Integer)
Me.Min = min
Me.Max = max
Me.Value = value
End Sub
Public Overloads Function CompareTo(other As Merge) As Integer Implements IComparable(Of Merge).CompareTo
If other Is Nothing Then Return 1
' Could use
' Return (Me.Value).CompareTo(other.Value)
' instead of the following code...
If Me.Value > other.Value Then Return 1
If Me.Value = other.Value Then Return 0
Return -1
End Function
End Class
I guess that you want a different criterion for the comparison.

How to make Arrays of Object in VB.net?

I am unable to debug this.
Is my procedure of array-making wrong?
The error it shows is
System.NullReferenceException :'Object reference not set to an instance of an object'
Module Module1
Class Toy
Private Name, ID As String
Private Price As Single
Private MinimumAge As Integer
Dim Count As Integer
Public Sub New()
End Sub
Public Sub SetName(ByVal N As String)
Name = N
End Sub
Public Sub SetID(ByVal I As String)
'VALIDATION CHECK FOR ID !
While Len(I) < 4
Console.WriteLine("Kindly Enter the ID with Lenght of Max 4 Characters")
End While
ID = I
End Sub
Public Sub SetPrice(ByVal SP As Single)
Price = SP
End Sub
Public Sub SetUserAge(ByVal SM As Integer)
'VALIDATION CHECK FOR AGE
While SM < 0 Or SM < 18
Console.WriteLine("Minimum age is 18")
End While
MinimumAge = SM
End Sub
Public Function GetName()
Return Name(Count)
End Function
Public Function GetID()
Return ID
End Function
Public Function GetPrice()
Return Price
End Function
Public Function GetAge()
Return MinimumAge
End Function
End Class
Class ComputerGame
Inherits Toy
Private Category, Consol As String
Public Sub New()
End Sub
Public Sub SetConsole(ByVal C As String)
While C <> "PS4" Or C <> "XBOX"
Console.WriteLine("Invalid console entered")
End While
Consol = C
End Sub
Public Sub SetCategory(ByVal SC As String)
Category = SC
End Sub
Public Function GetConsole()
Return Consol
End Function
Public Function GetCategory()
Return Category
End Function
End Class
Class Vehicle
Inherits Toy
Private Type As String
Private Length, Height, Weight As Single
Public Sub New()
End Sub
Public Sub SetType(ByVal TY As String)
While TY <> "Car" Or TY <> "Bus" Or TY <> "Truck"
Console.WriteLine("vehicle is not in list !")
End While
Type = TY
End Sub
Public Sub SetLength(ByVal SL As Single)
Length = SL
End Sub
Public Sub SetHeight(ByVal SH As Single)
Height = SH
End Sub
Public Sub SetWeight(ByVal SW As Integer)
Weight = SW
End Sub
Public Function GetT()
Return Type
End Function
Public Function GetLength()
Return Length
End Function
Public Function GetHeight()
Return Height
End Function
Public Function GetWeight()
Return Weight
End Function
End Class
Sub Main()
Dim ThisGame(6) As Toy
ThisGame(6) = New Toy
ThisGame(1).SetID("23456a")
ThisGame(2).SetID("236789b")
Console.WriteLine(ThisGame(1).GetID)
Console.ReadKey()
End Sub
End Module
This is what your Toy class should look like using the long method of Properties. Notice that you have Public Properties and a private field to store the values (sometimes called a backer field) Each Property has a Get and a Set procedure where you can add your validation code.
Public Class Toy
Private _Name As String
Public Property Name As String
Get
Return _Name
End Get
Set(value As String)
_Name = value
End Set
End Property
Private _ID As String
Public Property ID As String
Get
Return _ID
End Get
Set(value As String)
If value.Length < 4 Then
Console.WriteLine("Kindly Enter the ID with Lenght of Max 4 Characters")
Exit Property
End If
_ID = value
End Set
End Property
Private _Price As Single
Public Property Price As Single
Get
Return _Price
End Get
Set(value As Single)
_Price = value
End Set
End Property
Private _MinimumAge As Integer
Public Property MinimumAge As Integer
Get
Return _MinimumAge
End Get
Set(value As Integer)
If value < 0 Or value < 18 Then
Console.WriteLine("Minimum age is 18")
Exit Property
End If
_MinimumAge = value
End Set
End Property
Private _Count As Integer
Public Property Count As Integer
Get
Return _Count
End Get
Set(value As Integer)
_Count = value
End Set
End Property
End Class
Current versions have Automatic Properties where the compiler provides the getter, setter, and backer fields. Notice that you can still write your own Get and Set where you need to add code.
Public Class Toy
Public Property Name As String
Public Property Price As Single
Public Property Count As Integer
Private _ID As String
Public Property ID As String
Get
Return _ID
End Get
Set(value As String)
If value.Length < 4 Then
Console.WriteLine("Kindly Enter the ID with Lenght of Max 4 Characters")
Exit Property
End If
_ID = value
End Set
End Property
Private _MinimumAge As Integer
Public Property MinimumAge As Integer
Get
Return _MinimumAge
End Get
Set(value As Integer)
If value < 0 Or value < 18 Then
Console.WriteLine("Minimum age is 18")
Exit Property
End If
_MinimumAge = value
End Set
End Property
End Class
Please read the in line comments in regards to your array.
Sub Main()
Dim ThisGame(6) As Toy 'You are defining and array with 7 elements of Toy
'Each element is Nothing at this point and you will get a null exception error if you
'try to access an element.
ThisGame(6) = New Toy 'Here you have created an instance of your class for the 7th element
'The other six elements are still nothing so the next 3 lines will get
'NullReferenceException: 'Object reference not set to an instance of an object.'
'ThisGame(1).ID = "23456a"
'ThisGame(2).ID = "236789b"
ThisGame(1) = New Toy
ThisGame(1).ID = "23456a"
ThisGame(2) = New Toy
ThisGame(2).ID = "236789b"
'Remember the first element in you array is ThisGame(0)
Console.WriteLine(ThisGame(1).ID)
Console.ReadKey()
End Sub

vb.net arraylist of objects

I am trying to create an arraylist of objects, and everything is working fine except that the value of all 11 objects are the same. I've have tried multiple ways of writing the code, but I get the same outcome everytime.
arrFullProdList collects the name of all the products in the database. This is working properly. arrProducts is where I am having the issue with all the objects being the same.
What am I doing wrong?
Declared
Private objReader As SqlDataReader
Private objProducts As New CProducts
Private arrFullProdList As ArrayList = New ArrayList
Public arrProdcuts As ArrayList = New ArrayList
class CProduct
Public Class CProduct
Private _pstrProdId As String
Private _pstrProdDesc As String
Private _psngWhCost As Single
Private _psngRetPrice As Single
Private _pblnTaxable As Boolean
Private _isNewProd As Boolean
Public Sub New()
'_pstrProdId = ""
'_pstrProdDesc = ""
'_psngWhCost = 0
'_psngRetPrice = 0
'_pblnTaxable = False
'_isNewProd = False
End Sub
Public Property strProdId() As String
Get
Return _pstrProdId
End Get
Set(strVal As String)
_pstrProdId = strVal
End Set
End Property
Public Property strProdDesc() As String
Get
Return _pstrProdDesc
End Get
Set(strVal As String)
_pstrProdDesc = strVal
End Set
End Property
Public Property sngWhCost() As Single
Get
Return _psngWhCost
End Get
Set(sngVal As Single)
_psngWhCost = sngVal
End Set
End Property
Public Property sngRetPrice() As Single
Get
Return _psngRetPrice
End Get
Set(sngVal As Single)
_psngRetPrice = sngVal
End Set
End Property
Public Property blnTaxable() As Boolean
Get
Return _pblnTaxable
End Get
Set(blnVal As Boolean)
_pblnTaxable = blnVal
End Set
End Property
Public Property IsNewProd() As Boolean
Get
Return _isNewProd
End Get
Set(blnVal As Boolean)
_isNewProd = blnVal
End Set
End Property
Public ReadOnly Property GetSaveParameters() As ArrayList
Get
Dim paramList As New ArrayList
paramList.Add(New SqlClient.SqlParameter("ProdId", _pstrProdId))
paramList.Add(New SqlClient.SqlParameter("ProdDesc", _pstrProdDesc))
paramList.Add(New SqlClient.SqlParameter("WhCost", _psngWhCost))
paramList.Add(New SqlClient.SqlParameter("RetPrice", _psngRetPrice))
paramList.Add(New SqlClient.SqlParameter("Taxable", _pblnTaxable))
Return paramList
End Get
End Property
Public Function Save() As Integer
'return -1 if the ID already exists and we can't create a new record
If _isNewProd Then
Dim strRes As String = myDB.GetSingleValueFromSP("sp_CheckProdIDExists", _
New SqlClient.SqlParameter("ProdId", _pstrProdId))
If Not strRes = 0 Then
Return -1 'ID NOT unique!!
End If
End If
'if not a new member or it is new and is unique, then do the save (update or insert)
Return myDB.ExecSP("sp_SaveProduct", GetSaveParameters)
End Function
End Class
class CProducts
Imports System.Data.SqlClient
Public Class CProducts
'This class represents the Members table and the associated business rules
Private _Product As CProduct
'constructor
Public Sub New()
'instantiate the CMember object
_Product = New CProduct
End Sub
Public ReadOnly Property CurrentObject() As CProduct
Get
Return _Product
End Get
End Property
Public Sub Clear()
_Product = New CProduct
End Sub
Public Sub CreateNewProduct() 'call me when you are clearing the screen to create a new member
Clear()
_Product.IsNewProd = True
End Sub
Public Function Save() As Integer
Return _Product.Save
End Function
Public Function GetProductList() As SqlDataReader
Return myDB.GetDataReaderBySP("dbo.sp_GetProductList")
End Function
Public Function GetProducIdList() As SqlDataReader
Return myDB.GetDataReaderBySP("dbo.sp_GetProductIdList")
End Function
Public Function GetProductByName(strProdDesc As String) As CProduct
Dim params As New ArrayList
Dim param1 As New SqlParameter("proddesc", strProdDesc)
params.Add(param1)
FillObject(myDB.GetDataReaderBySP("dbo.sp_GetProductByName", params))
Return _Product
End Function
Public Function GetProductById(strProdId As String) As CProduct
Dim aParam As New SqlParameter("ProdId", strProdId)
FillObject(myDB.GetDataReaderBySP("dbo.sp_GetProductByID", aParam))
Return _Product
End Function
Public Function FillObject(sqlDR As SqlDataReader) As CProduct
Using sqlDR
If sqlDR.Read Then
With _Product
.strProdId = sqlDR.Item("ProdId") & ""
.strProdDesc = sqlDR.Item("ProdDesc") & ""
.sngWhCost = sqlDR.Item("WhCost") & ""
.sngRetPrice = sqlDR.Item("RetPrice") & ""
.blnTaxable = sqlDR.Item("Taxable") & ""
End With
Else
'failed for some reason
End If
End Using
Return _Product
End Function
'----------Start Alex's Code---------
Public Function GetProductByDesc(strProdDesc As String) As SqlDataReader
Dim aParam As New SqlParameter("ProdDesc", strProdDesc)
Return myDB.GetDataReaderBySP("dbo.sp_GetProductByDesc", aParam)
End Function
End Class
main
Private Sub LoadProducts()
arrProdcuts.Clear()
objReader = objProducts.GetProductByDesc(txtSearch.Text)
While objReader.Read
arrFullProdList.Add(objReader.Item("prodDesc"))
End While
objReader.Close()
For i = 0 To arrFullProdList.Count - 1
Dim aNewProd As CProduct
aNewProd = objProducts.GetProductByName(arrFullProdList.Item(i).ToString)
arrProdcuts.Add(aNewProd)
Next
End Sub

VB.NET Databinding Label not listening to PropertyChanged

I have a label which is databound to a Property in a Class which returns the highest number from another class. The numbers are filled into the other class from 6 text fields.
The 6 properties for the numbers all raise the PropertyChanged event when modified with the Parameter "BestThrow.Distance" as can be seen below:
Public Property Distance() As String
Get
If Status = ThrowStatus.Valid Then
If (m_Distance > 0) Then
Return m_Distance
Else
Return Nothing
End If
ElseIf Status = ThrowStatus.Pass Then
Return "PASS"
ElseIf Status = ThrowStatus.Foul Then
Return "FOUL"
Else
Return Nothing
End If
End Get
Set(value As String)
If (value.Length > 0) Then
If (IsNumeric(value)) Then
m_Distance = value
Status = ThrowStatus.Valid
ElseIf (value = "FOUL") Then
Status = ThrowStatus.Foul
ElseIf (value = "PASS") Then
Status = ThrowStatus.Pass
Else
Status = ThrowStatus.Valid
m_Distance = Nothing
End If
Else
m_Distance = Nothing
Status = ThrowStatus.Valid
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("BestThrow.Distance"))
End Set
End Property
The Property which the label is bound to is as follows:
Public ReadOnly Property BestThrow() As Object
Get
Dim bt = Throws.Where(Function(t) t.Status = ThrowStatus.Valid).OrderByDescending(Function(t) t.Distance).First()
If (IsNothing(bt.Distance)) Then
bt.Distance = "0"
End If
Return bt
End Get
End Property
For the sake of providing all the information the Databinding for the label is as follows
best.DataBindings.Add(New Binding("text", athlete, "BestThrow.Distance", False, DataSourceUpdateMode.OnPropertyChanged))
When I add in the first Distance the label updates fine and displays Distance(0).Distance as expected, however when I change the other 5 values the label will not update. If, however, I then modify the first value again it will recalculate the BestThrow and display the correct distance.
I have also tried manually raising the event using a button on the form which also did not work.
From using watch values I can see that when requested the BestThrow Property is spitting out the correct values, it just seems the label only is interested in an event from the first Throw.
Thank you in advance for any assistance.
Current Code:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class Competition
Public Sub New()
Competitors = New List(Of Competitor)()
End Sub
Public Property Competitors() As List(Of Competitor)
Get
Return m_Competitors
End Get
Set(value As List(Of Competitor))
m_Competitors = value
End Set
End Property
Private m_Competitors As List(Of Competitor)
Public ReadOnly Property CurrentPlacings() As List(Of Competitor)
Get
Return Competitors.OrderByDescending(Function(c) c.BestThrow).ToList()
End Get
End Property
End Class
Public Class Competitor
Public Sub New()
Throws = New List(Of [Throw])()
End Sub
Public Property athletenum() As Integer
Get
Return m_athnum
End Get
Set(value As Integer)
m_athnum = value
End Set
End Property
Private m_Athnum As Integer
Public Property FirstName() As String
Get
Return m_FirstName
End Get
Set(value As String)
m_FirstName = value
End Set
End Property
Private m_FirstName As String
Public Property LastName() As String
Get
Return m_LastName
End Get
Set(value As String)
m_LastName = value
End Set
End Property
Private m_LastName As String
Public Property compNumber() As String
Get
Return m_compNumb
End Get
Set(value As String)
m_compNumb = value
End Set
End Property
Private m_compNumb As String
Public Property club() As String
Get
Return m_club
End Get
Set(value As String)
m_club = value
End Set
End Property
Private m_club As String
Public Property Throws() As List(Of [Throw])
Get
Return m_Throws
End Get
Set(value As List(Of [Throw]))
m_Throws = value
End Set
End Property
Private m_Throws As List(Of [Throw])
Public ReadOnly Property BestThrow() As String
Get
Dim list As IList(Of [Throw]) = (From t As [Throw] In Throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Descending).ToList()
If (list.Count > 0) Then
If (IsNothing(list(0).Distance)) Then
Return "0"
Else
Return list(0).Distance
End If
End If
Return "0"
End Get
End Property
Public ReadOnly Property getLabel()
Get
Return compNumber & " " & LastName & ", " & FirstName & vbCrLf & club
End Get
End Property
Public Property currentPlace
Get
Return m_curplace
End Get
Set(value)
m_curplace = value
End Set
End Property
Private m_curplace As Integer
End Class
Public Enum ThrowStatus
Valid
Pass
Foul
End Enum
Public Class [Throw]
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
'Throw Status
Public Property Status() As ThrowStatus
Get
Return m_Status
End Get
Set(value As ThrowStatus)
m_Status = value
End Set
End Property
Private m_Status As ThrowStatus
'Throw Distance
Public Property Distance() As String
Get
Dim value As String = Me.m_Distance
If Status = ThrowStatus.Valid Then
If (m_Distance > 0) Then
value = m_Distance
Else
value = Nothing
End If
ElseIf Status = ThrowStatus.Pass Then
value = "PASS"
ElseIf Status = ThrowStatus.Foul Then
value = "FOUL"
Else
value = Nothing
End If
If (value Me.m_Distance) Then
Me.Distance = value
End If
Return value
End Get
Set(value As String)
If (value.Length > 0) Then
If (IsNumeric(value)) Then
m_Distance = value
Status = ThrowStatus.Valid
ElseIf (value = "FOUL") Then
Status = ThrowStatus.Foul
ElseIf (value = "PASS") Then
Status = ThrowStatus.Pass
Else
Status = ThrowStatus.Valid
m_Distance = Nothing
End If
Else
m_Distance = Nothing
Status = ThrowStatus.Valid
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("BestThrow"))
End Set
End Property
Private m_Distance As Decimal
Public Property attempt() As Integer
Get
Return m_attempt
End Get
Set(value As Integer)
m_attempt = value
End Set
End Property
Private m_attempt As Integer
End Class
Current binding:
best.DataBindings.Add(New Binding("text", athlete, "BestThrow", False, DataSourceUpdateMode.OnPropertyChanged))
First things first, I don't like seeing properties calculating the return value. I think this should be done in a method. (It might be okay in a read only property.) Just my opinion. What I find strange is that the getter for Distance depends on Status, and the setter acts more like a setter for Status.
Your binding expects a PropertyDescriptor named Distance. You return a String, and as you know the string class doesn't have a property named Distance.
best.DataBindings.Add(New Binding("text", athlete, "BestThrow.Distance", False, DataSourceUpdateMode.OnPropertyChanged))
Option 1
For this to work you'll need to do it like this:
Public ReadOnly Property BestThrow() As TYPE_OF_YOUR_CLASS
Get
'IMPORTANT:
'This approach requires at least ONE matching object and
'ONE item returned. If not, the binding will fail.
Return (From t As TYPE_OF_YOUR_CLASS In throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Ascending).First()
End Get
End Property
Option 2
Another option is to just bind to the BestThrow property:
best.DataBindings.Add(New Binding("text", athlete, "BestThrow", False, DataSourceUpdateMode.OnPropertyChanged))
And change your property to this:
Public ReadOnly Property BestThrow() As String
Get
'NOTE:
'Do not use First() as this will fail if no matching items are found.
'We use ToList() and check the count property.
Dim list As IList(Of TYPE_OF_YOUR_CLASS) = (From t As TYPE_OF_YOUR_CLASS In throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Ascending).ToList()
If (list.Count > 0) Then
Return list(0).Distance
End If
Return "0"
End Get
End Property
EDIT
To be honest I think this is bad designed. Please look in detail at this approach:
Public Class Athlete
'TODO: Implements INotifyPropertyChanged
Public Sub New(athleteNumber As Integer, Optional firstName As String = Nothing, Optional ByVal lastName As String = Nothing, Optional ByVal club As String = Nothing)
Me.m_athleteNumber = athleteNumber
Me.m_firstName = If((firstName Is Nothing), String.Empty, firstName)
Me.m_lastName = If((lastName Is Nothing), String.Empty, lastName)
Me.m_club = If((club Is Nothing), String.Empty, club)
End Sub
Public ReadOnly Property AthleteNumber() As Integer
Get
Return Me.m_athleteNumber
End Get
End Property
Public Property Club() As String
Get
Return Me.m_club
End Get
Set(value As String)
Me.m_club = value
'TODO: If changed raise property changed.
End Set
End Property
Public Property FirstName() As String
Get
Return Me.m_firstName
End Get
Set(value As String)
Me.m_firstName = value
'TODO: If changed raise property changed.
End Set
End Property
Public Property LastName() As String
Get
Return Me.m_lastName
End Get
Set(value As String)
Me.m_lastName = value
'TODO: If changed raise property changed.
End Set
End Property
Public Overrides Function ToString() As String
Return String.Concat(Me.m_firstName, " ", Me.m_lastName).Trim()
End Function
Private m_athleteNumber As Integer
Private m_club As String
Private m_firstName As String
Private m_lastName As String
End Class
Public Class Competitor
Implements INotifyPropertyChanged
Public Sub New(athlete As Athlete, Optional competitorNumber As String = Nothing)
If (athlete Is Nothing) Then
Throw New ArgumentNullException("athlete")
End If
Me.m_athlete = athlete
Me.m_bestThrow = 0D
Me.m_competitorNumber = If((competitorNumber Is Nothing), String.Empty, competitorNumber)
Me.m_curplace = 0
Me.m_throws = New ObjectCollection(Of [Throw])
AddHandler Me.m_throws.CollectionChanged, New CollectionChangeEventHandler(AddressOf Me.OnThrowCollectionChanged)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public ReadOnly Property Athlete() As Athlete
Get
Return Me.m_athlete
End Get
End Property
Public ReadOnly Property BestThrow() As Decimal
Get
Return Me.m_bestThrow
End Get
End Property
Public Property CompetitorNumber() As String
Get
Return Me.m_competitorNumber
End Get
Set(value As String)
If (value <> Me.m_competitorNumber) Then
Me.m_competitorNumber = value
Me.RaisePropertyChanged("CompetitorNumber")
End If
End Set
End Property
Public Property CurrentPlace() As Integer
Get
Return Me.m_curplace
End Get
Set(value As Integer)
If (value <> Me.m_curplace) Then
Me.m_curplace = value
Me.RaisePropertyChanged("CurrentPlace")
End If
End Set
End Property
Public ReadOnly Property Throws() As ObjectCollection(Of [Throw])
Get
Return Me.m_throws
End Get
End Property
Protected Sub OnThrowCollectionChanged(sender As Object, e As CollectionChangeEventArgs)
Dim list As IList(Of [Throw]) = (From t As [Throw] In Me.m_throws Select t Where t.Status = ThrowStatus.Valid Order By t.Distance Descending).ToList()
Dim bestThrow As Decimal = If((list.Count > 0), list(0).Distance, 0D)
If (Me.m_bestThrow <> bestThrow) Then
Me.m_bestThrow = bestThrow
Me.RaisePropertyChanged("BestThrow")
End If
End Sub
Private Sub RaisePropertyChanged(propertyName As String)
If (Not Me.PropertyChangedEvent Is Nothing) Then
Me.PropertyChangedEvent.Invoke(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
Private m_athlete As Athlete
Private m_bestThrow As Decimal
Private m_competitorNumber As String
Private m_curplace As Integer
Private m_throws As ObjectCollection(Of [Throw])
End Class
Public Class [Throw]
Public Sub New(Optional ByVal status As ThrowStatus = ThrowStatus.Valid, Optional distance As Decimal = 0D)
Me.m_status = status
Me.m_distance = Math.Max(distance, 0D)
End Sub
Public ReadOnly Property Status() As ThrowStatus
Get
Return Me.m_status
End Get
End Property
Public ReadOnly Property Distance() As Decimal
Get
Return Me.m_distance
End Get
End Property
Public Overrides Function ToString() As String
Select Case Me.m_status
Case ThrowStatus.Valid
Return Me.m_distance.ToString("N2")
Case ThrowStatus.Pass
Return "PASS"
Case ThrowStatus.Foul
Return "FOUL"
Case Else
Return 0D.ToString("N2")
End Select
End Function
Private m_attempt As Integer
Private m_distance As Decimal
Private m_status As ThrowStatus
End Class
Public Class ObjectCollection(Of T)
Inherits Collections.ObjectModel.Collection(Of T)
Public Event CollectionChanged As CollectionChangeEventHandler
Protected Overrides Sub ClearItems()
MyBase.ClearItems()
Me.RaiseCollectionChanged(CollectionChangeAction.Refresh, Nothing)
End Sub
Protected Overrides Sub InsertItem(index As Integer, item As T)
If (item Is Nothing) Then
Throw New ArgumentNullException("item")
ElseIf (Me.Contains(item)) Then
Throw New ArgumentException("Item duplicate.", "item")
End If
MyBase.InsertItem(index, item)
Me.RaiseCollectionChanged(CollectionChangeAction.Add, item)
End Sub
Protected Overridable Sub OnCollectionChanged(e As CollectionChangeEventArgs)
If (Not Me.CollectionChangedEvent Is Nothing) Then
Me.CollectionChangedEvent.Invoke(Me, e)
End If
End Sub
Private Sub RaiseCollectionChanged(action As CollectionChangeAction, item As T)
Me.OnCollectionChanged(New CollectionChangeEventArgs(action, item))
End Sub
Protected Overrides Sub RemoveItem(index As Integer)
Dim item As T = Me.Item(index)
MyBase.RemoveItem(index)
Me.RaiseCollectionChanged(CollectionChangeAction.Remove, item)
End Sub
Protected Overrides Sub SetItem(index As Integer, item As T)
Throw New NotSupportedException()
End Sub
End Class
Public Enum ThrowStatus As Integer
Valid = 0 '<- This is the default value.
Pass = 1
Foul = 2
End Enum
The following code are tested and works. Drop a Button and a TextBox onto a Form and append:
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.competitor = New Competitor(New Athlete(1, "John", "Smith", "Team JS"), "CP_1")
Me.competitor.Throws.Add(New [Throw](distance:=123.3472D))
Me.competitor.Throws.Add(New [Throw](distance:=424.1234D))
Me.competitor.Throws.Add(New [Throw](distance:=242.1234D))
Dim b As New Binding("Text", Me.competitor, "BestThrow", True, DataSourceUpdateMode.Never)
AddHandler b.Format, New ConvertEventHandler(AddressOf Me._FormatBinding)
Me.TextBox1.DataBindings.Add(b)
End Sub
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Me.competitor.Throws.Add(New [Throw](distance:=845.4365D))
End Sub
Private Sub _FormatBinding(sender As Object, e As ConvertEventArgs)
If ((TypeOf e.Value Is Decimal) AndAlso (e.DesiredType Is GetType(String))) Then
e.Value = CDec(e.Value).ToString("N2")
End If
End Sub
Private competitor As Competitor
End Class