Inserting item into list causes item to be deleted - vb.net

I currently am trying to insert a score into its proper position. The scores add to their proper position.
However when I add or insert to the contained list it does not expand past its deserialized size. (Note this highscore class is serialized to XML after the AddNewScore function is called. Also it is deserialized when the program is loaded)
The issue only occurs after deserialization.
Here is my serialization code (its contained in a serialization class that manages saving and loading all data types I use)
Public Sub LoadHighScores()
_highScores = DirectCast(LoadXml(highScoreFilePath, highScoreBackupFilePath, _highScoreFileCorrupted, GetType(HighScores)), HighScores)
If (_highScores.scores.Count < 5) Then
_highScores.AddNewScore(New Score("Steve", 9000))
_highScores.AddNewScore(New Score("John", 8000))
_highScores.AddNewScore(New Score("Paul", 7000))
_highScores.AddNewScore(New Score("Alex", 6000))
_highScores.AddNewScore(New Score("Joe", 5000))
SaveHighScores()
End If
End Sub
Private Sub SaveHighScores()
If (File.Exists(highScoreFilePath) And Not _highScoreFileCorrupted) Then
File.Copy(highScoreFilePath, highScoreBackupFilePath, True) 'Backup quiz data
End If
Using FileStream As FileStream = New FileStream(highScoreFilePath, FileMode.Create)
'Dim encryptionStream As CryptoStream = New CryptoStream(FileStream, AESCrypto.CreateEncryptor(Key, IV), CryptoStreamMode.Write)
Dim serializer As XmlSerializer = New XmlSerializer(GetType(HighScores))
serializer.Serialize(FileStream, _highScores)
_highScoreFileCorrupted = False
'encryptionStream.Close()
End Using
End Sub
Public Function LoadXml(ByVal filePath As String, ByVal backupFilePath As String, ByRef fileCorrupt As Boolean, ByVal dataType As Type) As Object
Dim returnValue As Object = Activator.CreateInstance(dataType)
If (File.Exists(filePath) Or File.Exists(backupFilePath)) Then
Using fileStream As FileStream = New FileStream(filePath, FileMode.OpenOrCreate)
Using backupFileStream As FileStream = New FileStream(backupFilePath, FileMode.OpenOrCreate)
Dim serializer As XmlSerializer = New XmlSerializer(dataType)
Try
returnValue = serializer.Deserialize(fileStream)
Catch ex As Exception
fileCorrupt = True
Try
returnValue = serializer.Deserialize(backupFileStream)
Catch e As Exception
MessageBox.Show("Backup Corrupted", "Loading error", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Try
End Using
End Using
End If
Return returnValue
End Function
Here is my high score class
Imports System.Collections.Generic
Imports System.Xml
Public Class HighScores
Public scores As List(Of Score) = New List(Of Score)
Public userplays As List(Of UserGameLog) = New List(Of UserGameLog)
'Public userPlays As Dictionary(Of String, Integer) = New Dictionary(Of String, Integer)
Sub New()
End Sub
Private Sub UpdatePlays(ByVal Username As String)
For Each userlog As UserGameLog In userplays
If (userlog.username = Username) Then
userlog.numberOfPlays += 1
Return
End If
Next
userplays.Add(New UserGameLog(Username))
End Sub
Public Function GetNumberOfPlays(ByVal Username As String) As Integer
For Each userlog As UserGameLog In userplays
If (userlog.username = Username) Then
Return userlog.numberOfPlays
End If
Next
Return 0
End Function
Public Function GetHighScores() As List(Of Score)
Return scores
End Function
Public Sub AddNewScore(ByVal score As Score)
UpdatePlays(score.username)
If (scores.Count > 0) Then
For index = 0 To scores.Count - 1
If (scores(index).score < score.score) Then
scores.Insert(index, score)
Exit Sub
End If
Next
End If
scores.Add(score)
End Sub
End Class

Solved my issue, on loading the highscore list i was pulling out the 5 highest scores. Instead of using range of I was assigning a new list the score list and removing the unused values to trim it down to 5 with
Public Function GetHighScores(ByVal maxScoreCount As Integer) As List(Of Score)
Dim scores As List(Of Score) = New List(Of Score)
scores = _highScores.scores
If (scores.Count > maxScoreCount) Then
scores.RemoveRange(maxScoreCount - 1, scores.Count - maxScoreCount)
End If
Return scores
End Function
This was a reference to the prior list and it was cutting out my values. Swapped it out with GetRange and problem is fixed. smacks head

Related

VB.NET Group by two columns and write results to an array

I need to group csv data to new csv by column values. I can do it by only one column, but unfortunately it is not enough, because I got duplicates and not achieve my goal. Here is my csv example, there is about 50 columns and last here is column(29) in my input csv:
603;10453;2.12.2020;88,69
603;10453;2.12.2020;88,69
603;10453;4.12.2020;72,69
605;10441;3.12.2020;39,51
605;10441;8.12.2020;25,85
605;10441;9.12.2020;52,91
605;10441;10.12.2020;66,31
605;10441;10.12.2020;66,31
606;10453;11.12.2020;72,69
606;10453;11.12.2020;72,69
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;1.12.2020;250,98
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;2.12.2020;274,02
607;11202;3.12.2020;165,29
607;11202;3.12.2020;165,29
607;11202;3.12.2020;165,29
607;11202;3.12.2020;165,29
607;11202;4.12.2020;75,87
607;11202;5.12.2020;123,24
607;11202;5.12.2020;123,24
607;11202;5.12.2020;123,24
607;11202;7.12.2020;88,69
607;11202;7.12.2020;88,69
And here is my code, where I group values by last column:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim inputFile = "input.csv"
Dim outputFile = "output.csv"
IO.File.WriteAllLines(outputFile, IO.File.ReadLines(inputFile).
Select(Function(x) x.Split(";"c)).
GroupBy(Function(x) {x(0), x(3)}).
Select(Function(x)
Return String.Format(
"{0};{1};{2};{3}",
x.Select(Function(y) y(0)).First,
x.Select(Function(y) y(1)).First,
x.Select(Function(y) y(2)).First,
x.Select(Function(y) y(3)).First)
End Function).ToArray)
End Sub
As you can see in the last column duplicate values and I need group this file by two keys, one of them column(0) or column(1) values and second one is column(3). But I can't figure out how I can I do it with my code.
Desiret outout file have to looks like this:
603;10453;2.12.2020;88,69
603;10453;4.12.2020;72,69
605;10441;3.12.2020;39,51
605;10441;8.12.2020;25,85
605;10441;9.12.2020;52,91
605;10441;10.12.2020;66,31
606;10453;11.12.2020;72,69
607;11202;1.12.2020;250,98
607;11202;2.12.2020;274,02
607;11202;3.12.2020;165,29
607;11202;4.12.2020;75,87
607;11202;5.12.2020;123,24
607;11202;7.12.2020;88,69
Usualy I have to remove duplicates if column(0) and column(2) if they has match.
Thanks for help!
I would use an object oriented approach. Make a wrapper object around each line that does the parsing and provides properties for each value, and then group the result as desired (I choose again the object oriented approach with an equality comparer and distinct).
As I don't know the meaning of the columns I simply assumed something: OrderNo, CustomerNo, OrderDate and Value.
Here's the code for the wrapper class:
Private Class Record
'Constructors
Public Sub New(lineNo As Int32, line As String)
Const expectedColumnCount As Int32 = 4
Const delimiter As String = ";"
If (lineNo < 1) Then Throw New ArgumentOutOfRangeException(NameOf(lineNo), lineNo, "The line number must be positive!")
If (line Is Nothing) Then Throw New ArgumentNullException(NameOf(line))
Dim tokens As String() = Split(line, delimiter, expectedColumnCount + 1, CompareMethod.Binary)
If (tokens.Length <> expectedColumnCount) Then Throw New ArgumentException($"Line {lineNo}: Invalid data row! {expectedColumnCount} '{delimiter}'-delimitered columns expected.")
Me.Tokens = tokens
End Sub
'Public Properties
Public ReadOnly Property OrderNo As String
Get
Return Tokens(0)
End Get
End Property
Public ReadOnly Property CustomerNo As String
Get
Return Tokens(1)
End Get
End Property
Public ReadOnly Property OrderDate As String
Get
Return Tokens(2)
End Get
End Property
Public ReadOnly Property Value As String
Get
Return Tokens(3)
End Get
End Property
'Private Properties
Private ReadOnly Property Tokens As String()
End Class
And this is the comparer that does the grouping:
Private Class RecordComparer
Implements IEqualityComparer(Of Record)
Private Sub New()
End Sub
Public Shared ReadOnly Property Singleton As New RecordComparer()
Public Function Equals(x As Record, y As Record) As Boolean Implements IEqualityComparer(Of Record).Equals
If (Object.ReferenceEquals(x, y)) Then Return True
If (x Is Nothing) OrElse (y Is Nothing) Then Return False
Return Comparer.Equals(x.OrderNo, y.OrderNo) AndAlso Comparer.Equals(x.CustomerNo, y.CustomerNo) AndAlso Comparer.Equals(x.Value, y.Value)
End Function
Public Function GetHashCode(obj As Record) As Integer Implements IEqualityComparer(Of Record).GetHashCode
If (obj Is Nothing) Then Return 42
Return Comparer.GetHashCode(obj.OrderNo) Xor Comparer.GetHashCode(obj.CustomerNo) Xor Comparer.GetHashCode(obj.Value)
End Function
Private Shared ReadOnly Comparer As IEqualityComparer(Of String) = StringComparer.Ordinal
End Class
and finally the usage:
'Convert input lines to simple objects
Dim i As Int32 = 1
Dim dataRows As New List(Of Record)()
For Each line As String In File.ReadLines(inputFile)
Dim data As New Record(i, line)
dataRows.Add(data)
i += 1
Next
'Group by the 3 columns (the DateTime is kind of random, no guarantee which object wins)
Dim consolidatedRows As IEnumerable(Of Record) = dataRows.Distinct(SimpleInputDataComparer.Singleton)
'Convert and export lines
Dim outputLines As IEnumerable(Of String) = consolidatedRows.Select(Function(e) $"{e.OrderNo};{e.CustomerNo};{e.OrderDate};{e.Value}")
File.WriteAllLines(outputFile, outputLines)
I got it work. For my goal I used Christoph example. Finaly my code looks like this:
Public Class TempClass
Public Property ID As String
Public Property day As String
Public Property OriginalStr As String
End Class
Public Class TempIDComparer
Implements IEqualityComparer(Of TempClass)
Private Function IEqualityComparer_Equals(x As TempClass, y As TempClass) As Boolean Implements IEqualityComparer(Of TempClass).Equals
If ReferenceEquals(x, y) Then
Return True
End If
If ReferenceEquals(x, Nothing) OrElse ReferenceEquals(y, Nothing) Then
Return False
End If
Return x.ID = y.ID AndAlso x.day = y.day
End Function
Private Function IEqualityComparer_GetHashCode(obj As TempClass) As Integer Implements IEqualityComparer(Of TempClass).GetHashCode
If obj Is Nothing Then Return 0
Return obj.ID.GetHashCode()
End Function
End Class
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
Dim inputFile = "input.csv"
Dim outputFile = "output.csv"
Dim list As List(Of TempClass) = New List(Of TempClass)()
Dim ls As List(Of String()) = New List(Of String())()
Dim fileReader As StreamReader = New StreamReader(inputFile)
Dim strLine As String = ""
While strLine IsNot Nothing
strLine = fileReader.ReadLine()
If strLine IsNot Nothing AndAlso strLine.Length > 0 Then
Dim t As TempClass = New TempClass() With {
.ID = strLine.Split(";"c)(0),
.day = strLine.Split(";"c)(3),
.OriginalStr = strLine
}
list.Add(t)
End If
End While
fileReader.Close()
Dim tempList = list.Distinct(New TempIDComparer())
Dim fileWriter As StreamWriter = New StreamWriter(outputFile, False, System.Text.Encoding.Default)
For Each item In tempList.ToList()
fileWriter.WriteLine(item.OriginalStr)
Next
fileWriter.Flush()
fileWriter.Close()
End Sub

Populate class from query on VB Net

Help translate C# code from this link Simplest way to populate class from query in C# to VB Net.
Option Infer On
Imports System.Reflection
Private Sub Main()
Dim connectionString = "..."
Dim records = (New Query(connectionString)).SqlQuery(Of TVChannel)("select top 10 * from TVChannel")
End Sub
Private Class TVChannel
Public Property number() As String
Public Property title() As String
Public Property favoriteChannel() As String
Public Property description() As String
Public Property packageid() As String
Public Property format() As String
End Class
Public Class Query
Private ReadOnly _connectionString As String
Public Sub New(ByVal connectionString As String)
_connectionString = connectionString
End Sub
Public Function SqlQuery(Of T)(ByVal query As String) As List(Of T)
Dim result = New List(Of T)()
Using connection = New SqlConnection(_connectionString)
connection.Open()
Using command = connection.CreateCommand()
command.CommandText = query
Using reader = command.ExecuteReader()
Dim columns = Enumerable.Range(0, reader.FieldCount).Select(Function(f) reader.GetName(f)).ToArray()
Dim properties = GetType(T).GetProperties()
Do While reader.Read()
Dim data = New Object(reader.FieldCount - 1){}
reader.GetValues(data)
Dim instance = DirectCast(Activator.CreateInstance(GetType(T)), T)
For i = 0 To data.Length - 1
If data(i) Is DBNull.Value Then
data(i) = Nothing
End If
Dim [property] = properties.SingleOrDefault(Function(x) x.Name.Equals(columns(i), StringComparison.InvariantCultureIgnoreCase))
If [property] IsNot Nothing Then
[property].SetValue(instance, Convert.ChangeType(data(i), [property].PropertyType))
End If
Next i
result.Add(instance)
Loop
End Using
End Using
End Using
Return result
End Function
End Class
but, I got error on this line
Dim instance = DirectCast(Activator.CreateInstance(GetType(T)), T)
System.MissingMethodException: 'No parameterless constructor defined for this object.'
This is a much better pattern to follow. It addresses at least four issues in the original code (sql injection, Nothing vs null, constructor access, unnecessary allocations):
Public Module SQL
Private ReadOnly _connectionString As String = "..."
Public Iterator Function Query(Of T)(ByVal query As String, translate As Func(IDataRecord, T), ParamArray data() As SqlParameter) As IEnumerable(Of T)
Using connection As New SqlConnection(_connectionString), _
command As New SqlCommand(query, connection)
If data IsNot Nothing Then command.Parameters.AddRange(data)
connection.Open()
Using reader As SqlDataReader = command.ExecuteReader()
While reader.Read()
Yield translate(reader)
End While
reader.Close()
End Using
End Using
End Function
End Module
Call it like this:
Private Sub Main()
Dim records = SQL.Query("select top 10 * from TVChannel",
Function(r)
'Yes, you're doing the mapping manually now for each query.
'But this lets you properly account for things NULL, column name mismatches, computed properties, etc.
Return New TVChannel With {
.number = r["number"],
.title = r["title"],
.favoriteChannel = r["favoriteChannel"],
.description = r["description"],
.packageid = r["packageid"],
.format = r["format"]
}
End Function,
Nothing)
For Each channel As TVChannel In records
Console.WriteLine($"Channel {channel.number}, {channel.title}")
Next
End Sub

Why does the combobox throws error when button associated with its event is clicked

Basic rundown of program: connected to a database. Combo box is populated with a terms list, after you select a term a get term button is pressed which populates a listview and then returns total balance due in a text box at the bottom of the form.
The Combobox populates, but when the btn is pressed catch ex throws an error along the lines of string cannot be converted to integer(occurs on the form design code). I'm not quite sure where I've gone wrong. It doesn't seem to catch anything else anywhere.
I'll include the code below
Imports Payables
Public Class Form1
Dim invoiceList As List(Of Invoices)
Dim termList As List(Of Terms)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LoadComboBoxes()
End Sub
Private Sub LoadComboBoxes()
termList = TermsDB.GetTermsList
cboTerms.DataSource = termList
cboTerms.ValueMember = "TermsID"
cboTerms.DisplayMember = "Description"
End Sub
Private Sub btnGetInvoice_Click(sender As Object, e As EventArgs) Handles btnGetInvoice.Click
Dim invoiceList As List(Of Invoices)
Dim TermsID = CInt(cboTerms.SelectedValue)
Try
invoiceList = InvoicesDB.FindInvoiceByID(TermsID)
txtTotalBalanceDue.Text = FormatCurrency(InvoicesDB.GetBalanceDue())
If invoiceList.Count > 0 Then
Dim invoice As Invoices
For i = 0 To invoiceList.Count - 1
invoice = invoiceList(i)
lvInvoices.Items.Add(invoice.InvoiceID)
lvInvoices.Items(i).SubItems.Add(invoice.VendorID)
lvInvoices.Items(i).SubItems.Add(invoice.InvoiceNumber)
lvInvoices.Items(i).SubItems.Add(invoice.InvoiceDate)
lvInvoices.Items(i).SubItems.Add(invoice.InvoiceTotal)
lvInvoices.Items(i).SubItems.Add(invoice.PaymentTotal)
lvInvoices.Items(i).SubItems.Add(invoice.CreditTotal)
lvInvoices.Items(i).SubItems.Add(invoice.TermsID)
lvInvoices.Items(i).SubItems.Add(invoice.DueDate)
lvInvoices.Items(i).SubItems.Add(invoice.PaymentDate)
Next
Else
MessageBox.Show("There is no info on this account")
Me.Close()
End If
Catch ex As Exception
Throw ex
Me.Close()
End Try
End Sub
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Me.Close()
End Sub
End Class
Public Class Invoices
Dim m_InvoiceID As Integer
Dim m_VendorID As Integer
Dim m_InvoiceNumber As String
Dim m_InvoiceDate As Date
Dim m_InvoiceTotal As Decimal
Dim m_PaymentTotal As Decimal
Dim m_CreditTotal As Decimal
Dim m_TermsID As Integer
Dim m_DueDate As Date
Dim m_PaymentDate As Date
Public Sub New()
End Sub
Public Property InvoiceID() As Integer
Get
Return m_InvoiceID
End Get
Set(value As Integer)
m_InvoiceID = value
End Set
End Property
Public Property VendorID() As Integer
Get
Return m_VendorID
End Get
Set(value As Integer)
m_VendorID = value
End Set
End Property
Public Property InvoiceNumber() As String
Get
Return m_InvoiceNumber
End Get
Set(value As String)
m_InvoiceNumber = value
End Set
End Property
Public Property InvoiceDate() As Date
Get
Return m_InvoiceDate
End Get
Set(value As Date)
m_InvoiceDate = value
End Set
End Property
Public Property InvoiceTotal() As Decimal
Get
Return m_InvoiceTotal
End Get
Set(value As Decimal)
m_InvoiceTotal = value
End Set
End Property
Public Property PaymentTotal() As Integer
Get
Return m_PaymentTotal
End Get
Set(value As Integer)
m_PaymentTotal = value
End Set
End Property
Public Property CreditTotal() As Integer
Get
Return m_CreditTotal
End Get
Set(value As Integer)
m_CreditTotal = value
End Set
End Property
Public Property TermsID() As Integer
Get
Return m_TermsID
End Get
Set(value As Integer)
m_TermsID = value
End Set
End Property
Public Property DueDate() As Date
Get
Return m_DueDate
End Get
Set(value As Date)
m_DueDate = value
End Set
End Property
Public Property PaymentDate() As Date
Get
Return m_PaymentDate
End Get
Set(value As Date)
m_PaymentDate = value
End Set
End Property
'Create a function BalanceDue to return the BalanceDue
Public Function GetBalanceDue() As Decimal
Return m_InvoiceTotal - m_PaymentTotal - m_CreditTotal
End Function
End Class
Imports System.Data.SqlClient
Public Class InvoicesDB
Public Shared Function FindInvoiceByID(ByVal TermsID) As List(Of Invoices)
Dim invoice As New Invoices
Dim connection As SqlConnection = PayablesDB.GetConnection
Dim invoiceList As New List(Of Invoices)
Dim selectStatement As String = "SELECT InvoiceID, VendorID, InvoiceNumber, InvoiceDate, InvoiceTotal,PaymentTotal,CreditTotal,TermsID,DueDate,PaymentDate FROM Invoices WHERE TermsID=#TermsID"
Dim selectCommand As New SqlCommand(selectStatement, connection)
'add the parameter to the parameter collection of the command object
selectCommand.Parameters.AddWithValue("#TermsID", TermsID)
Try
connection.Open()
Dim reader As SqlDataReader = selectCommand.ExecuteReader
If reader.Read Then
invoice.InvoiceID = CInt(reader("InvoiceID"))
invoice.VendorID = CInt(reader("VendorID"))
invoice.InvoiceNumber = CInt(reader("InvoiceNumber"))
invoice.InvoiceDate = CDate(reader("InvoiceDate"))
invoice.InvoiceTotal = CDec(reader("InvoiceTotal"))
invoice.PaymentTotal = CDec(reader("PaymentTotal"))
invoice.CreditTotal = CDec(reader("CreditTotal"))
invoice.TermsID = CInt(reader("TermsID"))
invoice.DueDate = CDate(reader("DueDate"))
invoice.PaymentDate = CDate(reader("PaymentDate"))
Else
'that means the invoice is not found
invoice = Nothing 'this means the vendor object no longer exists
End If
reader.Close()
connection.Close()
Catch ex As Exception
Throw ex
End Try
Return invoiceList
End Function
Public Shared Function GetBalanceDue() As Decimal 'aggregate
Dim connection As SqlConnection = PayablesDB.GetConnection
Dim selectCommand As New SqlCommand()
selectCommand.Connection = connection
selectCommand.CommandText =
"SELECT SUM(InvoiceTotal - PaymentTotal - CreditTotal) " &
"AS BalanceDue FROM Invoices" &
"WHERE TermsID=#TermsID"
connection.Open()
Dim balanceDue As Decimal = CDec(selectCommand.ExecuteScalar)
connection.Close()
Return balanceDue
End Function
End Class
Public Class Terms
Dim m_TermsID As Integer
Dim m_Description As String
Dim m_DueDays As Integer
Public Sub New()
End Sub
Public Property TermsID() As Integer
Get
Return m_TermsID
End Get
Set(ByVal value As Integer)
m_TermsID = value
End Set
End Property
Public Property Description() As String
Get
Return m_Description
End Get
Set(ByVal value As String)
m_Description = value
End Set
End Property
Public Property DueDays() As Integer
Get
Return m_DueDays
End Get
Set(ByVal value As Integer)
m_DueDays = value
End Set
End Property
End Class
Imports System.Data.SqlClient
Public Class TermsDB
Public Shared Function GetTermsList() As List(Of Terms)
Dim termList As New List(Of Terms)
Dim connection As SqlConnection = PayablesDB.GetConnection
Dim selectStatement As String =
"SELECT TermsID,Description,DueDays " &
"FROM Terms " &
"ORDER BY Description"
Dim selectCommand As New SqlCommand(selectStatement, connection)
Try
connection.Open()
Dim reader As SqlDataReader = selectCommand.ExecuteReader()
Dim term As Terms
Do While reader.Read
term = New Terms
term.TermsID = CInt(reader("TermsID"))
term.Description = reader("Description").ToString
term.DueDays = CInt(reader("DueDays"))
termList.Add(term)
Loop
reader.Close()
Catch ex As SqlException
Throw ex
Finally
connection.Close()
End Try
Return termList
End Function
End Class
I think your button is triggering the page load event, and you are losing the selected combobox value.
Do you have a text value as the first option in the combobox? That would cause the conversion error you're seeing.
Avoid binding the combobox on postback with something like
If ispostback = false Then
LoadComboBoxes()
End If

Populating a combo box with a list of functions - Need Advice

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.

trying to query a database

I'm having a bit of a problem populating a collections class with the values from the database. Everytime I loop through a record in the WHILE DR.READ loop, the last record over writes all the other items in the collection. My returnVal collections has several of the same items despite the loop showing each individual record being added into returnVal. Thanks for any help.
Public Shared Function getStuff(ByVal sb As StringBuilder) As System.Collections.Generic.List(Of Minutes)
Dim returnVal As New System.Collections.Generic.List(Of Minutes)
Dim conn As New SqlConnection
Dim cmd As New SqlCommand
Dim dr As SqlDataReader
conn.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString
cmd.Connection = conn
cmd.CommandText = sb.ToString
Try
conn.Open()
dr = cmd.ExecuteReader
While dr.Read
Dim _minutes As New Minutes
_minutes.Minutes = dr("minutes")
_minutes.MinutesId = dr("minutesId")
returnVal.Add(_minutes)
End While
Catch ex As Exception
Dim _minutes As New Minutes
_minutes.Minutes = ex.ToString
_minutes.MinutesId = 0
returnVal.Add(_minutes)
End Try
conn.Close()
Return returnVal
End Function
This is my Minutes Class
Imports Microsoft.VisualBasic
Public Class Minutes
Private Shared _minutesId As Integer
Private Shared _minutes As String
Public Property MinutesId() As Integer
Get
Return _minutesId
End Get
Set(ByVal value As Integer)
_minutesId = value
End Set
End Property
Public Property Minutes() As String
Get
Return _minutes
End Get
Set(ByVal value As String)
_minutes = value
End Set
End Property
Public Shared Function getStuff(ByVal sb As StringBuilder) As System.Collections.Generic.List(Of Minutes)
Return MinutesDA.getStuff(sb)
End Function
Public Shared Function modify(ByVal sb As StringBuilder) As String
Return MinutesDA.modify(sb)
End Function
Public Shared Property Id() As Integer
Get
Return MinutesDA.Id
End Get
Set(ByVal value As Integer)
MinutesDA.Id = value
End Set
End Property
Public Shared Property Index() As Integer
Get
Return MinutesDA.Index
End Get
Set(ByVal value As Integer)
MinutesDA.Index = value
End Set
End Property
End Class
The problem is that the fields in the Minutes class are shared. There will be one instance of the fields shared by all instances of the class. Remove the "Shared" from the fields:
Private _minutesId As Integer
Private _minutes As String
This will store the values for each instance separately
To confirm you are getting new values or the same value try this:
Dim i as Integer
i = 0
While dr.Read
Dim _minutes As New Minutes
_minutes.Minutes = dr("minutes")
' _minutes.MinutesId = dr("minutesId")
_minutes.MinutesId = i
i = i + 1
returnVal.Add(_minutes)
End While
After this you can see if the MinuteId is different.
N.B. In VB6 you had to set the _minutes to Nothing at the end of the loop to get a new instance. However I wouldn't have thought that would be true in VB.NET