DatagridView VB.Net, thread safety - vb.net

So i have this DataTable i have to show in a datagridview.
To do this i used to do the following line in the form_load
Me.Datagridview1.datasource = DT
It will work if i have to add a few rows, but when i add enough to make the scrollbar in the datagridview appear, it will freeze de program. No exceptions are thrown but my program will freeze and i think it is because im learning how to use threads.
Here i start listening another program that will send strings and i have to split them and put them into rows inside a datatable, and then show them.
Private Vstm As Stream
Dim tcp As New TcpClient
Dim DT As New DataTable("Acciones")
Dim Inter As New Interprete
Private Sub Conectar_Click(sender As Object, e As EventArgs) Handles Button1.Click
tcp.Connect("192.168.1.143", 8050)
Vstm = tcp.GetStream()
Dim VtcpThd As New Thread(AddressOf LeerSocket)
'Se arranca el subproceso.
VtcpThd.Start()
End Sub
Here in LeerSocket ill do the reading
Private Sub LeerSocket()
Dim VbufferDeLectura() As Byte
VbufferDeLectura = New Byte(100) {}
While True
'Se queda esperando a que llegue algĂșn mensaje.
Vstm.Read(VbufferDeLectura, 0, VbufferDeLectura.Length)
'Se evalĂșa que llega en la cabecera del mensaje.
Me.Inter.interpretar(Encoding.ASCII.GetString(VbufferDeLectura))
End While
End Sub
Here it is the class interprete, that will try to understand the string.
Public Class Interprete
Property Codigo As String
Property Cotizacion As Integer
Property Cabecera As String
Private VAcciones As DataTable
Public Property Acciones() As DataTable
Get
Return VAcciones
End Get
Set(ByVal value As DataTable)
VAcciones = value
End Set
End Property
Public Sub interpretar(Data As String)
Me.Cabecera = Nothing
Me.Codigo = Nothing
Dim buff() As String = Split(Data, "!!")
Dim arr() As String = Split(buff(0), "||")
Me.Cabecera = arr(0)
Me.Codigo = arr(1)
Select Case Cabecera
Case "COT"
Cotizaciones(arr)
End Select
End Sub
Public Sub Cotizaciones(M As String())
Dim DR As DataRow = Acciones.NewRow
Dim buffer(3) As Object
buffer(0) = ""
buffer(1) = 0
buffer(2) = M(1)
buffer(3) = M(2)
DR.ItemArray = buffer
Acciones.Rows.Add(DR)
End Sub
End Class
How can i use the datagridview safely?
Also can you guys recomend any books for vb?

You should invoke that data table action on the GUI thread:
Me.BeginInvoke(New Action(Sub()
Dim DR As DataRow = Acciones.NewRow
Dim buffer(3) As Object
buffer(0) = ""
buffer(1) = 0
buffer(2) = M(1)
buffer(3) = M(2)
DR.ItemArray = buffer
Acciones.Rows.Add(DR)
End Sub))

Related

If statement not moving to the next item

I am working on a side project in VB, it is a network monitoring tool to pings a number of devices which should come back as successful or failed. I have extreme limits in programming so forgive me.
I am using buttons, a total of 34 for each device that I want to ping that returns a success or fail which will color code green(success) and red(failed) but I am sure there is a better way? Right now, my code is stuck on one button, I cant figure out how to step to the next one on the list. In my code, I have it commented out of the results I want produced which is where I am stuck on.
The text file contains all my IP addresses I want to ping separated by a comma.
Basically, when the form is running, it will display each button as green or red, depending on if the device is online or not. I want the code to loop every 2 minutes as well to keep the devices up to date. Literally a device monitoring tool. I was able to get it to work using 34 different End If statements but that is messy and a lot of work to maintain. Any assistance would be helpful.
Using MyReader As New Microsoft.VisualBasic.FileIO.TextFieldParser("\\txt file location\device.txt")
MyReader.TextFieldType = FileIO.FieldType.Delimited
MyReader.SetDelimiters(",")
Dim currentRow As String()
Dim MyLen() As String = {"Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", "Button32", "Button33", "Button34"}
While Not MyReader.EndOfData
Try
currentRow = MyReader.ReadFields()
Dim currentField As String
For Each currentField In currentRow
If My.Computer.Network.Ping(currentField) Then
MsgBox(MyLen)
'MyLen = Color.LimeGreen
Else
MsgBox(MyLen)
'MyLen.Text = "Failed"
'MyLen.BackColor = Color.Red
End If
Next
Catch ex As Microsoft.VisualBasic.FileIO.MalformedLineException
MsgBox("Line " & ex.Message & "is not valid and will be skipped.")
End Try
End While
End Using
enter image description here
Here is some code that takes a different approach. To try it create a new Form app with only a FlowLayoutPanel and Timer on it. Use the default names. It might be above your skill level but using the debugger you might learn something. Or not.
Public Class Form1
Private MyButtons As New List(Of Button)
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
Timer1.Enabled = False 'started later
Timer1.Interval = CInt(TimeSpan.FromMinutes(2).TotalMilliseconds) '<<<< Change >>>>
Dim path As String = "\\txt file location\device.txt"
Dim text As String = IO.File.ReadAllText(path) 'use this
''for testing >>>>>
'Dim text As String = "10.88.0.70, 10.88.0.122,192.168.0.15, 10.88.0.254, 1.2.3.4" ''for testing
''for testing <<<<<
Dim spltCHs() As Char = {","c, " "c, ControlChars.Tab, ControlChars.Cr, ControlChars.Lf}
Dim IPs() As String = text.Split(spltCHs, StringSplitOptions.RemoveEmptyEntries)
For Each addr As String In IPs
Dim b As New Button
Dim p As New MyPinger(addr)
p.MyButton = b
b.Tag = p 'set tag to the MyPinger for this address
b.AutoSize = True
b.Font = New Font("Lucida Console", 10, FontStyle.Bold)
b.BackColor = Drawing.Color.LightSkyBlue
'center text in button
Dim lAddr As String = p.Address
Dim t As String = New String(" "c, (16 - lAddr.Length) \ 2)
Dim txt As String = t & lAddr & t
b.Text = txt.PadRight(16, " "c)
b.Name = "btn" & lAddr.Replace("."c, "_"c)
AddHandler b.Click, AddressOf SomeButton_Click 'handler for button
MyButtons.Add(b) 'add button to list
Next
'sort by IP
MyButtons = (From b In MyButtons
Select b Order By DirectCast(b.Tag, MyPinger).Address(True)).ToList
For Each b As Button In MyButtons
FlowLayoutPanel1.Controls.Add(b) 'add button to panel
Next
FlowLayoutPanel1.Anchor = AnchorStyles.Bottom Or AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top
Timer1.Enabled = True 'start the timer
End Sub
Private Sub SomeButton_Click(sender As Object, e As EventArgs)
'if button clicked ping it
Dim b As Button = DirectCast(sender, Button) 'which button
b.BackColor = MyPinger.UnknownColor
Dim myP As MyPinger = DirectCast(b.Tag, MyPinger) ''get the MyPinger for this
myP.DoPing() 'do the ping
End Sub
Private Async Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Timer1.Enabled = False
Dim myPs As New List(Of MyPinger)
For Each b As Button In MyButtons
b.BackColor = MyPinger.UnknownColor
Dim myP As MyPinger = DirectCast(b.Tag, MyPinger)
myPs.Add(myP)
Next
Dim t As Task
t = Task.Run(Sub()
Threading.Thread.Sleep(25)
For Each myP As MyPinger In myPs
myP.DoPing()
Next
End Sub)
Await t
Timer1.Enabled = True
End Sub
End Class
Public Class MyPinger
Public Shared ReadOnly UpColor As Drawing.Color = Drawing.Color.LightGreen
Public Shared ReadOnly DownColor As Drawing.Color = Drawing.Color.Red
Public Shared ReadOnly UnknownColor As Drawing.Color = Drawing.Color.Yellow
Private _ip As Net.IPAddress
Private _ping As Net.NetworkInformation.Ping
Public LastReply As Net.NetworkInformation.PingReply
Private Shared ReadOnly PingTMO As Integer = 2500
Private _waiter As New Threading.AutoResetEvent(True)
Public MyButton As Button
Public Sub New(IPAddr As String)
Me._ip = Net.IPAddress.Parse(IPAddr) 'will throw exception if IP invalid <<<<<
Me._ping = New Net.NetworkInformation.Ping 'create the ping
'do initial ping
Dim t As Task = Task.Run(Sub()
Threading.Thread.Sleep(25) 'so init has time
Me.DoPingAsync()
End Sub)
End Sub
Private Async Sub DoPingAsync()
If Me._waiter.WaitOne(0) Then 'only one at a time for this IP
Me.LastReply = Await Me._ping.SendPingAsync(Me._ip, PingTMO)
Dim c As Drawing.Color
Select Case Me.LastReply.Status
Case Net.NetworkInformation.IPStatus.Success
c = UpColor
Case Else
c = DownColor
End Select
Me.SetButColor(c)
Me._waiter.Set()
End If
End Sub
Public Sub DoPing()
Me.DoPingAsync()
End Sub
Private Sub SetButColor(c As Drawing.Color)
If Me.MyButton IsNot Nothing Then
If Me.MyButton.InvokeRequired Then
Me.MyButton.BeginInvoke(Sub()
Me.SetButColor(c)
End Sub)
Else
Me.MyButton.BackColor = c
End If
End If
End Sub
Public Function TheIP() As Net.IPAddress
Return Me._ip
End Function
Public Function Address(Optional LeadingZeros As Boolean = False) As String
Dim rv As String = ""
If LeadingZeros Then
Dim byts() As Byte = Me._ip.GetAddressBytes
For Each b As Byte In byts
rv &= b.ToString.PadLeft(3, "0"c)
rv &= "."
Next
Else
rv = Me._ip.ToString
End If
Return rv.Trim("."c)
End Function
End Class

Using corresponding value of picturebox to delete access record

Hey I've created a dictionary that I use to create pictureboxes. Each picturebox has a number, which I then want to use to remove the access record, corresponding to that number. So I've got the dictionary working, but how do I get the number that corresponds to each picturebox? Here is my code (everything is defined, like cardsdictionary as dictionary):
Public Sub bigpictureloader()
'Dim list As New List(Of String)(cardsdictionary.Keys)
Dim cardcount As Integer
cardcount = 0
counter += 1
cardcount = counter
'Dim cards As List(Of String) = New List(Of String)
cardsdictionary.Add(imageurltxt.Text, cardcount)
'Create a placeholder variable
Dim cardPictureBox As PictureBox
Dim pair As KeyValuePair(Of String, Integer)
'Loop through every selected card URL
For Each pair In cardsdictionary
'Create a new PictureBox
cardPictureBox = New PictureBox()
cardPictureBox.Size = New Size(100, 100)
cardPictureBox.SizeMode = PictureBoxSizeMode.Zoom
cardPictureBox.WaitOnLoad = False
AddHandler cardPictureBox.Click, AddressOf imagehandler
'Add the PictureBox to the Form
Me.Controls.Add(cardPictureBox)
'MsgBox(cardsdict.Values.ToString)
If imageurltxt.Text = "" Then
cardPictureBox = Nothing
Else
cardPictureBox.LoadAsync(pair.Key)
TableLayoutPanel1.Controls.Add(cardPictureBox, 0, 0)
End If
Next
End Sub
Private Sub testdelete()
'THIS SAVES TO THE DEBUG ACCESS DATABASE!!!!!
Using conn As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source = FULL YUGIOH ACCESS DATABASE.accdb;")
Using command As New OleDbCommand("Delete From cmon11 Where ID= #ID;", conn)
'pair.value is what I think will work, but doesn't currently
command.Parameters.Add("#ID", OleDbType.Integer).Value = CInt(pair.value)
conn.Open()
command.ExecuteNonQuery()
End Using
End Using
End Sub
this is my imagehandler!!!
Private Sub imagehandler(Sender As Object, e As EventArgs)
testdelete()
End Sub
You can use the Tag property of the PictureBox to store the ID. You populate this property when you create the PictureBox.
In your handler you can do something like this:
Private Sub imagehandler(Sender As Object, e As EventArgs)
Dim myPictureBox As PictureBox = CType(sender, PictureBox)
'Now you can access myPictureBox.Tag and pass it into your delete function
Dim id As Integer = -1
If Int32.TryParse(myPictureBox.Tag, id) = True Then
testdelete(id)
End If
End Sub
Private Sub testdelete(id As Integer)
'THIS SAVES TO THE DEBUG ACCESS DATABASE!!!!!
Using conn As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source = FULL YUGIOH ACCESS DATABASE.accdb;")
Using command As New OleDbCommand("Delete From cmon11 Where ID= #ID;", conn)
'pair.value is what I think will work, but doesn't currently
command.Parameters.Add("#ID", OleDbType.Integer).Value = id
conn.Open()
command.ExecuteNonQuery()
End Using
End Using
End Sub
testdelete doesn't know which imasge you want to delete. Using Michael's suggestion you want:
command.Parameters.Add("#ID", OleDbType.Integer).Value = CInt(Me.Tag)
By the way your use of counter and cardcount can be cleaned up substantially.

Import very large .csv to List array, then copy to DataTable

I am trying to import a large CSV file, where I am dumping each row of the input csv file into an array (vector), which is NumColumns long. I fetched some code to copy a list to a DataTable, however, I am not sure the IList (IsEnumerable?) is needed. I also haven't looked into what T is.
My gut feeling is that I can go to some other code I have to load a DataTable with row and column data from a 2-dimensional array x(,), but for some reason I think there may be a fast way to simply .add(x), i.e. add the entire row vector to the DataTable to keep the speed up. You don't want to loop through columns(?)
Below is the code which will open up any .csv.
Imports System.ComponentModel
Imports System.IO
Public Class Form1
Dim NumColumns As Integer
Dim ColumnNames() As String
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim filename As String = Nothing
With OpenFileDialog1
.FileName = "*.csv"
.CheckFileExists = True
.ShowReadOnly = True
.Filter = "Comma delimited *.csv|*.csv"
If .ShowDialog = DialogResult.OK Then
filename = .FileName
End If
End With
Dim csvreader As New StreamReader(filename)
Dim inputLine As String = ""
inputLine = csvreader.ReadLine()
Dim buff() As String = Split(inputLine, ",")
NumColumns = UBound(buff)
ReDim ColumnNames(UBound(buff) + 1)
For j As Integer = 0 To NumColumns
ColumnNames(j + 1) = buff(j)
Next
inputLine = csvreader.ReadLine()
Do While inputLine IsNot Nothing
Dim rowdata = New MyDataArray(NumColumns)
Dim csvArray() As String = Split(inputLine, ",")
For i As Integer = 0 To NumColumns
rowdata.x(i) = csvArray(i)
Next
MyDataArray.DataArray.Add(rowdata)
inputLine = csvreader.ReadLine()
Loop
Dim dgv As New DataGridView
dgv.DataSource = ToDataTable(MyDataArray.DataArray)
dgv.Width = 1000
dgv.Height = 1000
Me.Controls.Add(dgv)
End Sub
Public Shared Function ToDataTable(Of T)(data As IList(Of T)) As DataTable
Dim properties As PropertyDescriptorCollection = TypeDescriptor.GetProperties(GetType(T))
Dim dt As New DataTable()
For i As Integer = 0 To properties.Count - 1
Dim [property] As PropertyDescriptor = properties(i)
dt.Columns.Add([property].Name, [property].PropertyType)
Next
Dim values As Object() = New Object(properties.Count - 1) {}
For Each item As T In data
For i As Integer = 0 To values.Length - 1
values(i) = properties(i).GetValue(item)
Next
dt.Rows.Add(values(1))
Next
Return dt
End Function
End Class
Public Class MyDataArray
Public Shared DataArray As New List(Of MyDataArray)()
Public Property x() As Object
Sub New(ByVal cols As Integer)
ReDim x(cols)
End Sub
End Class
Maybe this code will help?
You can use OleDB to convert the CSV to a Database and then put it into a datatable. All you need is a DataGridView and form (If you want to print it). Then you can use teh code below to accomplish what you need to do.
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim file As String = "test.txt"
Dim path As String = "C:\Test\"
Dim ds As New DataSet
Try
If IO.File.Exists(IO.Path.Combine(path, file)) Then
Dim ConStr As String = _
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & _
path & ";Extended Properties=""Text;HDR=No;FMT=Delimited\"""
Dim conn As New OleDb.OleDbConnection(ConStr)
Dim da As New OleDb.OleDbDataAdapter("Select * from " & _
file, conn)
da.Fill(ds, "TextFile")
End If
Catch ex As Exception
MessageBox.Show(ex.ToString)
End Try
DataGridView1.DataSource = ds.Tables(0)
End Sub
End Class
However, you could always convert it to an xml and work from there
Imports System.IO
Module Module3
Public Function _simpleCSV2tbl(CSVfile As String) As DataTable
Dim watch As Stopwatch = Stopwatch.StartNew()
watch.Start()
'A,B,C,D,E
'00001,4,1,2,3560
'00002,4,12,1,2000
'00003,1,4,2,4500
'00004,4,12,1,2538.63
'00005,1,1,2,3400
'00006,2,5,2,2996.48
Dim dTable As New DataTable(CSVfile)
Using reader As New StreamReader(CSVfile)
Dim CSV1stLine As String = reader.ReadLine
Dim getCols = (From s In CSV1stLine.Split(",") Select s).ToList()
Dim setTblColumns = (From c In getCols Select dTable.Columns.Add(c, GetType(String))).ToList
Dim ReadToEnd As String = reader.ReadToEnd()
Dim getRows = (From s In ReadToEnd.Split(vbLf) Select s).ToArray
_setTblRows(getRows, dTable)
Console.WriteLine(String.Format("Elapsed: {0}", Format(watch.Elapsed.TotalMilliseconds, "F"), dTable.Rows.Count))
reader.Close()
reader.Dispose()
End Using
_ShowTbl(dTable, 10)
End Function
Public Function _setTblRows(getRows As String(), dTable As DataTable) As IEnumerable
_setTblRows = getRows.Select(Function(r) dTable.LoadDataRow(r.Split(","), False)).ToArray()
End Function
Public Sub _ShowTbl(ByVal dTable As DataTable, Optional ByVal rPad As Short = 0)
If dTable.TableName = Nothing Then dTable.TableName = "NoName"
If rPad = 0 Then
Console.WriteLine(String.Format("->Unformatted Table: {0}, Count={1}", dTable.TableName, dTable.Rows.Count))
Else
Console.WriteLine(String.Format("->Formatted Table: {0}, Count={1}", dTable.TableName, dTable.Rows.Count))
End If
_ShowTblColumns(dTable.Columns, rPad)
_ShowTblRows(dTable.Rows, rPad)
End Sub
Public Function _ShowTblColumns(ByVal TblColumns As DataColumnCollection, Optional ByVal rPad As Short = 0)
Dim getTblColumns = (From c As DataColumn In TblColumns Select c.ColumnName).ToList()
Console.WriteLine(String.Join(",", getTblColumns.Select(Function(s) String.Format(s.PadLeft(rPad, vbNullChar)).ToString).ToArray))
End Function
Public Function _ShowTblRow(ByVal Row As DataRow, Optional ByVal rPad As Short = 0)
Dim getRowFields = (From r In Row.ItemArray Select r).ToList
Console.WriteLine(String.Join(",", getRowFields.Select(Function(s) String.Format(s.PadLeft(rPad, vbNullChar)).ToString).ToArray))
End Function
Public Function _ShowTblRows(ByVal Rows As DataRowCollection, Optional ByVal rPad As Short = 0)
Dim rCount As Integer
For Each row As DataRow In Rows
_ShowTblRow(row, rPad)
rCount += 1
If rCount Mod 20 = 0 Then
If NewEscape(String.Format(" {0} out of {1}", rCount.ToString, (Rows.Count).ToString)) Then Exit Function
End If
Next row
Console.WriteLine()
End Function
Public Function _NewEscape(ByVal Message As String) As Boolean
Console.Write("{0} / Press any key to continue or Esc to quit {1}", Message, vbCrLf)
Dim rChar As Char = Console.ReadKey(True).KeyChar
Dim rEscape As Boolean
If rChar = Chr(27) Then rEscape = True
Return rEscape
End Function
End Module

value of type 'cfeedback' cannot be converted to 'system.collections.arraylist'

Firstly, i have a grdData at my main page. After choosing the data i want and went to another page using
Request.QueryString("id")
In that page i would like to make another grdData using the
Request.QueryString("id")
but came upon an error by
Value of type 'cfeedback' cannot be converted to 'system.collections.arraylist'
Below are my codes
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim objArrayList As New ArrayList
Dim objCDBFeedback As New CDBFeedback
Dim intGuestID2 As Integer
intGuestID2 = Request.QueryString("id")
objArrayList = objCDBFeedback.getFeedBack(intGuestID2)
grdResult.DataSource = objArrayList
grdResult.DataBind()
grdResult.HeaderRow.BackColor = Drawing.Color.AliceBlue
grdResult.RowStyle.BackColor = Drawing.Color.BlanchedAlmond
grdResult.AlternatingRowStyle.BackColor = Drawing.Color.LightSalmon
grdResult.Columns(0).Visible = True
End Sub
My Function
Public Function getFeedBack(ByVal pintGuestID1 As Integer) As CFeedback
Dim objCmd As New MySqlCommand
Dim objCn As New MySqlConnection(connectionString)
Dim objAdapter As New MySqlDataAdapter
Dim strSQL As String = ""
Dim objDs As New DataSet
Dim objDataRow As DataRow
strSQL = "SELECT * FROM tblFeedback WHERE strGuestCodeFB=" & pintGuestID1
objCmd.CommandText = strSQL
objCmd.Connection = objCn
objAdapter.SelectCommand = objCmd
objCn.Open()
objAdapter.Fill(objDs, "tblFeedback")
objDataRow = objDs.Tables("tblFeedback").Rows(0)
Dim objCFeedback As New CFeedback
objCFeedback.Feedback = objDataRow.Item("strGuestCompanyTI")
objCn.Close()
Return objCFeedback
End Function
My Class
Public Class CFeedback
Private strGuestCodeFB As Integer
Private strFeedBackFB As String
Public Property GuestId() As String
Get
Return strGuestCodeFB
End Get
Set(ByVal value As String)
strGuestCodeFB = value
End Set
End Property
Public Property Feedback() As String
Get
Return strFeedBackFB
End Get
Set(ByVal value As String)
strFeedBackFB = value
End Set
End Property
End Class
So is it possible to have a grdData base on querystring?
The very first thing that you need to do is edit your code behind and add the following two lines at the top:
Option Explicit On
Option Strict On
This will show you at least one error: assigning a type of CFeedback to a type of ArrayList.
You will need to determine what the appropriate resolution to this is, but I suspect that you want to return an ArrayList or generic List from GetFeedback instead of just the one item.
So, among other changes, you will want to change pageload to look something like:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim objCDBFeedback As New CDBFeedback
Dim intGuestID2 As Integer
intGuestID2 = CInt(Request.QueryString("id"))
Dim cValues As System.Collections.Generic.List(Of CFeedback)
cValues = objCDBFeedback.getFeedBack(intGuestID2)
grdResult.DataSource = cValues
grdResult.DataBind()
grdResult.HeaderRow.BackColor = Drawing.Color.AliceBlue
grdResult.RowStyle.BackColor = Drawing.Color.BlanchedAlmond
grdResult.AlternatingRowStyle.BackColor = Drawing.Color.LightSalmon
grdResult.Columns(0).Visible = True
grdResult.Visible = cValues.Count <> 0
End Sub
And the getFeeback method to look something like:
Public Function getFeedBack(ByVal pintGuestID1 As Integer) As System.Collections.Generic.List(Of CFeedback)
Dim cValues As New System.Collections.Generic.List(Of CFeedback)
Using objCn As New MySqlConnection(connectionString)
Using objCmd As New MySqlCommand
Dim strSQL As String = ""
strSQL = "SELECT * FROM tblFeedback WHERE strGuestCodeFB=" & pintGuestID1
objCmd.CommandText = strSQL
objCmd.Connection = objCn
objCn.Open()
Using oReader As MySqlDataReader = objCmd.ExecuteReader
Do While oReader.Read
Dim objCFeedback As New CFeedback
objCFeedback.Feedback = oReader.Item("strGuestCompanyTI")
cValues.Add(objCFeedback)
Loop
End Using
objCn.Close()
End Using
End Using
Return cValues
End Function

The best way to extract data from a CSV file into a searchable datastructure?

I have a csv file with 48 columns of data.
I need to open this file, place it into a data structure and then search that data and present it in a DataRepeater.
So far I have successfully used CSVReader to extract the data and bind it to myDataRepeater. However I am now struggling to place the data in a table so that I can filter the results. I do not want to use SQL or any other database.
Does anyone have a suggestion on the best way to do this?
So far, this is working in returning all records:
Private Sub BindCsv()
' open the file "data.csv" which is a CSV file with headers"
Dim dirInfo As New DirectoryInfo(Server.MapPath("~/ftp/"))
Dim fileLocation As String = dirInfo.ToString & "data.txt"
Using csv As New CsvReader(New StreamReader(fileLocation), True)
myDataRepeater.DataSource = csv
myDataRepeater.DataBind()
End Using
End Sub
Protected Sub myDataRepeater_ItemDataBound(ByVal sender As Object, ByVal e As RepeaterItemEventArgs) Handles myDataRepeater.ItemDataBound
Dim dataItem As String() = DirectCast(e.Item.DataItem, String())
DirectCast(e.Item.FindControl("lblPropertyName"), ITextControl).Text = dataItem(2).ToString
DirectCast(e.Item.FindControl("lblPrice"), ITextControl).Text = dataItem(7).ToString
DirectCast(e.Item.FindControl("lblPricePrefix"), ITextControl).Text = dataItem(6)
DirectCast(e.Item.FindControl("lblPropertyID"), ITextControl).Text = dataItem(1)
DirectCast(e.Item.FindControl("lblTYPE"), ITextControl).Text = dataItem(18)
DirectCast(e.Item.FindControl("lblBedrooms"), ITextControl).Text = dataItem(8)
DirectCast(e.Item.FindControl("lblShortDescription"), ITextControl).Text = dataItem(37)
Dim dirInfo As New DirectoryInfo(Server.MapPath("~/ftp/images/"))
DirectCast(e.Item.FindControl("imgMain"), Image).ImageUrl = dirInfo.ToString & "pBRANCH_" & dataItem(1) & ".jpg"
DirectCast(e.Item.FindControl("linkMap"), HyperLink).NavigateUrl = "http://www.multimap.com/map/browse.cgi?client=public&db=pc&cidr_client=none&lang=&pc=" & dataItem(5) & "&advanced=&client=public&addr2=&quicksearch=" & dataItem(5) & "&addr3=&addr1="
End Sub
Code add to filter results:
Try
Dim csv As New CSVFile(fileLocation)
Dim ds As DataSet = csv.ToDataSet("MyTable")
If Not ds Is Nothing Then
Dim strExpr As String = "Bedrooms >= '3'"
Dim strSort As String = "PropertyID ASC"
'Use the Select method to find all rows matching the filter.
Dim myRows() As DataRow
'myRows = Dt.Select(strExpr, strSort)
myRows = csv.ToDataSet("MyTable").Tables("MyTable").Select(strExpr, strSort)
myDataRepeater.DataSource = myRows
myDataRepeater.DataBind()
End If
Catch ex As Exception
End Try
Which does return the two rows I am expecting but then when it binds to the datarepeater I get the following error:
DataBinding: 'System.Data.DataRow' does not contain a property with the name 'PropertyName'.
Corrected code, filter not being applied:
Public Sub PageLoad(ByVal Sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Not Page.IsPostBack Then
ReadCsv()
lblSearch.Text = "Lettings Search"
End If
End Sub
Private Sub ReadCsv()
Dim dirInfo As New DirectoryInfo(Server.MapPath("~/ftp/"))
Dim fileLocation As String = dirInfo.ToString & "data.txt"
Try
Dim csv As New CSVFile(fileLocation)
Dim ds As DataSet = csv.ToDataSet("MyTable")
If Not ds Is Nothing Then
myDataRepeater.DataSource = ds
myDataRepeater.DataMember = ds.Tables.Item(0).TableName
myDataRepeater.DataBind()
End If
ds = Nothing
csv = Nothing
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
Protected Sub btnSubmit_Click(ByVal sender As Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles btnSubmit.Click
Dim rowCount As Integer
rowCount = QueryCsv()
pnlSearch.Visible = False
lblResults.Visible = True
lblSearch.Text = "Search Results"
lblResults.Text = "Your search returned " & rowCount.ToString & " results"
If rowCount > 0 Then
myDataRepeater.Visible = True
pnlResults.Visible = True
btnBack.Visible = True
End If
End Sub
Protected Function QueryCsv() As Integer
Dim dirInfo As New DirectoryInfo(Server.MapPath("~/ftp/"))
Dim fileLocation As String = dirInfo.ToString & "data.txt"
Dim numberofRows As Integer
Try
Dim csv As New CSVFile(fileLocation)
Dim ds As DataSet = csv.ToDataSet("MyTable")
If Not ds Is Nothing Then
Dim strExpr As String = "PropertyID = 'P1005'"
Dim strSort As String = "PropertyID DESC"
Try
ds.Tables.Item(0).DefaultView.RowFilter = strExpr
ds.Tables.Item(0).DefaultView.Sort = strSort
myDataRepeater.DataSource = ds.Tables.Item(0).DefaultView
Catch ex As Exception
End Try
End If
numberofRows = ds.Tables("MyTable").Rows.Count
Catch ex As Exception
End Try
Return numberofRows
End Function
Why not use the built-in TextFileParser to get the data into a DataTable? Something like Paul Clement's answer in this thread
One of the ways I've done this is by using a structure array and reflection.
First, set up your structure in a module: CSVFileFields.vb
Imports System.Reflection
Public Module CSVFileFields
#Region " CSV Fields "
Public Structure CSVFileItem
Dim NAME As String
Dim ADDR1 As String
Dim ADDR2 As String
Dim CITY As String
Dim ST As String
Dim ZIP As String
Dim PHONE As String
Public Function FieldNames() As String()
Dim rtn() As String = Nothing
Dim flds() As FieldInfo = Me.GetType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
If Not flds Is Nothing Then
ReDim rtn(flds.Length - 1)
Dim idx As Integer = -1
For Each fld As FieldInfo In flds
idx += 1
rtn(idx) = fld.Name
Next
End If
Return rtn
End Function
Public Function ToStringArray() As String()
Dim rtn() As String = Nothing
Dim flds() As FieldInfo = Me.GetType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
If Not flds Is Nothing Then
ReDim rtn(flds.Length - 1)
Dim idx As Integer = -1
For Each fld As FieldInfo In flds
idx += 1
rtn(idx) = fld.GetValue(Me)
Next
End If
Return rtn
End Function
Public Shadows Function ToString(ByVal Delimiter As String) As String
Dim rtn As String = ""
Dim flds() As FieldInfo = Me.GetType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
If Not flds Is Nothing Then
For Each fld As FieldInfo In flds
rtn &= fld.GetValue(Me) & Delimiter
Next
rtn = rtn.Substring(0, rtn.Length - 1)
End If
Return rtn
End Function
End Structure
#End Region
End Module
Next we will make our own collection out of the structure we just made. This will make it easy to use .Add() .Remove() etc for our structure. We can also remove individual items with .RemoveAt(Index). File: CSVFileItemCollection.vb
#Region " CSVFileItem Collection "
Public Class CSVFileItemCollection
Inherits System.Collections.CollectionBase
Public Sub Add(ByVal NewCSVFileItem As CSVFileItem)
Me.List.Add(NewCSVFileItem)
End Sub
Public Sub Remove(ByVal RemoveCSVFileItem As CSVFileItem)
Me.List.Remove(RemoveCSVFileItem)
End Sub
Default Public Property Item(ByVal index As Integer) As CSVFileItem
Get
Return Me.List.Item(index)
End Get
Set(ByVal value As CSVFileItem)
Me.List.Item(index) = value
End Set
End Property
Public Shadows Sub Clear()
MyBase.Clear()
End Sub
Public Shadows Sub RemoveAt(ByVal index As Integer)
Remove(Item(index))
End Sub
End Class
#End Region
Next you need your class to handle the reflection import: CSVFile.vb
Imports System.Reflection
Imports System.IO
Imports Microsoft.VisualBasic.PowerPacks
Public Class CSVFile
#Region " Private Variables "
Private _CSVFile As CSVFileItem, _Delimiter As String, _Items As New CSVFileItemCollection
#End Region
#Region " Private Methods "
Private Sub FromString(ByVal Line As String, ByVal Delimiter As String)
Dim CSVFileElements() As String = Line.Split(Delimiter)
If Not CSVFileElements Is Nothing Then
Dim fldInfo() As FieldInfo = _CSVFile.GetType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
If Not fldInfo Is Nothing Then
Dim itm As System.ValueType = CType(_CSVFile, System.ValueType)
For fldIdx As Integer = 0 To CSVFileElements.Length - 1
fldInfo(fldIdx).SetValue(itm, CSVFileElements(fldIdx).Replace(Chr(34), ""))
Next
_CSVFile = itm
Else
Dim itms As Integer = 0
If Not fldInfo Is Nothing Then
itms = fldInfo.Length
End If
Throw New Exception("Invalid line definition.")
End If
Else
Dim itms As Integer = 0
If Not CSVFileElements Is Nothing Then
itms = CSVFileElements.Length
End If
Throw New Exception("Invalid line definition.")
End If
End Sub
#End Region
#Region " Public Methods "
Public Sub New()
_CSVFile = New CSVFileItem
End Sub
Public Sub New(ByVal Line As String, ByVal Delimiter As String)
_CSVFile = New CSVFileItem
_Delimiter = Delimiter
FromString(Line, Delimiter)
End Sub
Public Sub New(ByVal Filename As String)
LoadFile(Filename)
End Sub
Public Sub LoadFile(ByVal Filename As String)
Dim inFile As StreamReader = File.OpenText(Filename)
Do While inFile.Peek > 0
FromString(inFile.ReadLine, ",")
_Items.Add(_CSVFile)
_CSVFile = Nothing
Loop
inFile.Close()
End Sub
#End Region
#Region " Public Functions "
Public Function ToDataSet(ByVal TableName As String) As DataSet
Dim dsCSV As DataSet = Nothing
If Not _Items Is Nothing AndAlso _Items.Count > 0 Then
Dim flds() As FieldInfo = _Items.Item(0).GetType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
If Not flds Is Nothing Then
dsCSV = New DataSet
dsCSV.Tables.Add(TableName)
For Each fld As FieldInfo In flds
'Add Column Names
With dsCSV.Tables.Item(TableName)
.Columns.Add(fld.Name, fld.FieldType)
End With
Next
'Populate Table with Data
For Each itm As CSVFileItem In _Items
dsCSV.Tables.Item(TableName).Rows.Add(itm.ToStringArray)
Next
End If
End If
Return dsCSV
End Function
#End Region
#Region " Public Properties "
Public ReadOnly Property Item() As CSVFileItem
Get
Return _CSVFile
End Get
End Property
Public ReadOnly Property Items() As CSVFileItemCollection
Get
Return _Items
End Get
End Property
#End Region
End Class
Okay a little explanation. What this class is doing is first getting the line of delimited (",") text and splitting it into a string array. Then it iterates through every field you have in your structure CSVFileItem and based on the index populates that structure variable. It doesn't matter how many items you have. You could have 1 or 1,000 so long as the order in which your structure is declared is the same as the contents you are loading. For example, your input CSV should match CSVFileItem as "Name,Address1,Address2,City,State,Zip,Phone". That is done with this loop here from the above code:
Dim fldInfo() As FieldInfo = _CSVFile.GetType.GetFields(BindingFlags.Instance Or BindingFlags.Public)
If Not fldInfo Is Nothing Then
Dim itm As System.ValueType = CType(_CSVFile, System.ValueType)
For fldIdx As Integer = 0 To CSVFileElements.Length - 1
fldInfo(fldIdx).SetValue(itm, CSVFileElements(fldIdx).Replace(Chr(34), ""))
Next
_CSVFile = itm
Else
Dim itms As Integer = 0
If Not fldInfo Is Nothing Then
itms = fldInfo.Length
End If
Throw New Exception("Invalid line definition.")
End If
To make things easy, instead of having to load the file from our main class, we can simply pass it the file path and this class will do all of the work and return a collection of our structure. I know this seems like a lot of setup, but it's worth it and you can come back and change your original structure to anything and the rest of the code will still work flawlessly!
Now to get everything going. Now you get to see how easy this is to implement with only a few lines of code. File: frmMain.vb
Public Class Form1
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Try
Dim csv As New CSVFile("C:\myfile.csv")
Dim ds As DataSet = csv.ToDataSet("MyTable")
If Not ds Is Nothing Then
'Add Labels
Dim lblSize As New Size(60, 22), lblY As Integer = 10, lblX As Integer = 10, lblSpacing As Integer = 10
For Each fldName As String In csv.Items.Item(0).FieldNames
Dim lbl As New Label
lbl.AutoSize = True
lbl.Size = lblSize
lbl.Location = New Point(lblX, lblY)
lbl.Name = "lbl" & fldName
lblX += lbl.Width + lblSpacing
lbl.DataBindings.Add(New Binding("Text", ds.Tables.Item(0), fldName, True))
drMain.ItemTemplate.Controls.Add(lbl)
Next
drMain.DataSource = ds
drMain.DataMember = ds.Tables.Item(0).TableName
End If
ds = Nothing
csv = Nothing
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
End Class
This really makes for some dynamic programming. You can wrap these in generic classes and call the functions for any structure. You then have some reusable code that will make your programs efficient and cut down on programming time!
Edit:
Added the ability to dump structure collection to a dataset and then dynamically fill a datarepeater.
Hope this helps. (I know this was a lot of information and seems like a lot of work, but I guarantee you that once you get this in place, it will really cut down on future projects coding time!)