I am trying to fill a listbox with the contents of a .csv file by separating the two values per line and add to a structure.
Public Class Form1
Structure Members
Dim Number As Integer
Dim Name As String
End Structure
Dim Memberlist(30) As Members
Public Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim i As Integer = 0
For Each line As String In IO.File.ReadAllLines("MembershipPhone.txt")
Dim myData() = line.Split(","c)
Memberlist(i).Name = myData(0)
Memberlist(i).Number = myData(1)
ListBox1.Items.Add(myData(0))
i = i + 1
Next
ListBox1.SelectedItem = Nothing
End Sub
I see that you listed your question as C# when I think it may belong in VB.net. I can happily answer your question in C#....
First I would use a standard CSV reader. I find it to be easier and it can account for a bunch of different formats. I use the standard OleDbConnection and Microsoft JET engine to read to a database (How to read a CSV file into a .NET Datatable)
Once you have the Datatable you could bind it to your ListBox
//Load the data from the example above
DataTable myData = GetDataTableFromCsv(FilePath, true);
for (int i = 0; i < myData.Rows.Count; i++)
{
//Grab all my data for this row
string PersonID = (string)myData.Rows[i][0];
string FirstName = (string)myData.Rows[i][1];
string LastName = (string)myData.Rows[i][2];
//Add what I want to the listbox
ListBox1.Items.Add(FirstName);
}
//Select nothing from the listbox
ListBox1.SelectedIndex = -1;
Hope that was helpful! Good luck in .NET, it's a lot of fun, but I would recommend using C# over VB :-)
Related
I have a string or a text file which contains some software serial number among other information.
I’m trying to pull out the software serial numbers which all start with EAN- and send them to a listbox.
Example of the string or text file:
2
EAN-3E4R5-5TGGG-6667Y
Software name Technology
PO #PORD-11111
INV-219149
3
EAN-SXDR5-5DDD-6DDDY
Software name Technology
PO #PORD-11111
INV-219149
I'm after just the serial numbers beging with EAN-.
So the listbox would be
EAN-3E4R5-5TGGG-6667Y
EAN-SXDR5-5DDD-6DDDY
This code would work:
Dim strBuf As String
strBuf = "EAN-3E4R5-5TGGG-6667Y Software nam.............."
Dim Tokens1() As String
Tokens1 = Split(strBuf, "EAN-")
For I = 1 To UBound(Tokens1)
Dim OneListItem As String = "EAN-" & Split(Tokens1(I), " ")(0)
ListBox1.Items.Add(OneListItem)
Next
I read the file with .ReadAllLines which returns an array of the lines in the file. I looped through the lines and if a line started with EAN- it was added to the list. Then the list is bound to the ListBox.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lstSerialNums As New List(Of String)
Dim lines = File.ReadAllLines("C:\Users\maryo\Desktop\Code\Serial Numbers.txt")
For Each line In lines
If line.StartsWith("EAN-") Then
lstSerialNums.Add(line)
End If
Next
ListBox1.DataSource = lstSerialNums
End Sub
With a small amount of LINQ, you can make quite readable code:
Dim srcFile = "C:\temp\SO67550806.txt"
Dim eans = File.ReadLines(srcFile).Where(Function(s) s.StartsWith("EAN-")).ToList()
yourListBoxName.DataSource = eans
Right now I have many locations with this structure. At the moment I have: name as string and x,y,z positions as single. So it's a mix of data types and I might want to add both more data in the future and also other data types. I must be able to easily extract any part of this data.
Example of how I'll work with this data is: When I choose South Wales from a combobox then I want to get its properties, x,y,z populated in a textbox. So they need to be "linked". If I choose London then it'll have its x,y,z properties etc.
My initial idea is just to dim every single data such as in the first example below. This should be the easiest way with 100% control of what's what, and I could easily extract any single data but at the same time might get tedious I assume, or am I wrong? Is it a good way to approach this?
Dim SW_FP As String = "South Wales"
Dim SW_FP_X As Single = "489,1154"
Dim SW_FP_Y As Single = "-8836,795"
Dim SW_FP_Z As Single = "109,6124"
The next example below is something i just googled up. Is this a good method?
Dim dt As DataTable = New DataTable
dt.Columns.Add("South Wales", GetType(String))
dt.Columns.Add("489,1154", GetType(Single))
dt.Columns.Add("-8836,795", GetType(Single))
dt.Columns.Add("109,6124", GetType(Single))
OR should I use something else? Arrays, Objects with properties... and this is where my ideas end. Are there other methods? XML?
I want to do it in a smart way from start instead of risking to rewrite/recreate everything in the future. So my main question is: Which method would you suggest to be the smartest to choose? and also if you could provide a super tiny code example.
You mentioned that when you choose an item you want to get it's properties. This shows that you are looking for objects. If not using a database one example could be to make Location objects and have a List of these to be added or removed from. Then you have a lot of different ways to get the data back from the List. For example:
Class:
Public Class Location
Public Property Name As String
Public Property X As Single
Public Property Y As Single
Public Property Z As Single
End Class
List:
Dim locations As New List(Of Location)
Dim location As New Location With {
.Name = "South Wales",
.X = 1.1,
.Y = 1.2,
.Z = 1.3
}
locations.Add(location)
LINQ to get result:
Dim result = locations.SingleOrDefault(Function(i) i.Name = "South Wales")
This is just an example for use within your program, hope it helps.
Disclaimer: Untested code. It's more to guide you than copy-paste into your project.
First, create a Class that will represent the structured data:
Public Class Location
Public Property Name As String
Public Property PositionX As Single
Public Property PositionY As Single
Public Property PositionZ As Single
Public Sub New()
Me.New (String.Empty, 0, 0, 0)
End Sub
Public Sub New(name As String, x As Single, y As Single, z As Single)
Me.Name = name
Me.PositionX = x
Me.PositionY = y
Me.PositionZ = z
End Sub
Now, you can create a List(Of Location) and use that List to bind to a ComboBox, like this:
Dim list As New List(Of Location) = someOtherClass.ReadLocations ' Returns a List(Of Location) from your database, or file, or whatever.
cboLocations.DataSource = list
cboLocations.DisplayMember = "Name" ' The name of the Location class' Property to display.
cboLocations.ValueMember = "Name" ' Use the same Name Property since you have no ID.
You can also forego the list variable declaration like the following, but I wanted to show the declaration of list above:
cboLocations.DataSource = someOtherClass.ReadLocations
Function someOtherClass.ReadLocations() may populate the List(Of Locations) in a way similar to this. Note I'm not including data access code; this is just an example to show how to add Location objects to the List(Of Location):
Dim list As List(Of Location)
' Some loop construct
For each foo in Bar
Dim item As New Location(foo.Name, foo.X, foo.Y, foo.Z)
list.Add(item)
' End loop
Return list
The "magic" happens when you select an option from the ComboBox. I forget the ComboBox event offhand, so that's homework for you :-) You take the selected Object of the ComboBox and cast it back to the native type, in this case Location:
Dim item As Location = DirectCast(cboLocations.SelectedItem, Location)
txtName.Text = item.Name
txtPosX.Text = item.PositionX.ToString
txtPosY.Text = item.PositionY.ToString
txtPosZ.Text = item.PositionZ.ToString
Here is one way, using a DataTable as you mentioned. This is a stand alone example project just to show code used.
This example loads data from file is found and saves data on exit.
Form1 Image
' Stand alone example
' needs DataGridView1, Label1 and
' ComboBox1 on the Designer
' copy/replace this code with default
Option Strict On
Option Explicit On
Public Class Form1
Dim dt As New DataTable("Freddy")
Dim bs As New BindingSource
'edit path/filename to use as test data path
Dim filepath As String = "C:\Users\lesha\Desktop\TestData.xml"
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
dt.WriteXml(filepath)
End Sub
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
With dt
dt.Columns.Add("Country", GetType(String))
dt.Columns.Add("x", GetType(String))
dt.Columns.Add("y", GetType(String))
dt.Columns.Add("z", GetType(String))
' add extra column to hold concatenated
' location (could be a hidden column)
dt.Columns.Add("CombLoc", GetType(String), "'x = ' + x + ' y = ' + y + ' z = ' + z")
If IO.File.Exists(filepath) Then
' saved file found so load it
dt.ReadXml(filepath)
Else
' no saved file so make one test row
.Rows.Add("South Wales", 489.1154, -8836.795, 109.6124)
End If
End With
bs.DataSource = dt
DataGridView1.DataSource = bs
' set any properties for DataGridView1
With DataGridView1
' to hide Combined Location column
.Columns("CombLoc").Visible = False
' dontwant row headers
.RowHeadersVisible = False
End With
set up ComboBox
With ComboBox1
.DataSource = bs
' displayed item
.DisplayMember = "Country"
' returned item
.ValueMember = "CombLoc"
If .Items.Count > 0 Then .SelectedIndex = 0
End With
' default Label text
Label1.Text = "No items found"
End Sub
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
no items in list so exit sub
If ComboBox1.SelectedIndex < 0 Then Exit Sub
send returneditem to Label
Label1.Text = ComboBox1.SelectedValue.ToString
End Sub
End Class
The text file contains lines with the year followed by population like:
2016, 322690000
2015, 320220000
etc.
I separated the lines substrings to get all the years in a list box, and all the population amounts in a separate listbox, using the following code:
Dim strYearPop As String
Dim intYear As Integer
Dim intPop As Integer
strYearPop = popFile.ReadLine()
intYear = CInt(strYearPop.Substring(0, 4))
intPop = CInt(strYearPop.Substring(5))
lstYear.Items.Add(intYear)
lstPop.Items.Add(intPop)
Now I want to add the population amounts together, using the .Items to act as an array.
Dim intPop1 As Integer
intPop1 = lstPop.Items(0) + lstPop.Items(1)
But I get an error on lstPop.Items(1) and any item other than lstPop.Items(0), due to out of range. I understand the concept of out of range, but I thought that I create an index of several items (about 117 lines in the file, so the items indices should go up to 116) when I populated the list box.
How do i populate the list box in a way that creates an index of list box items (similar to an array)?
[I will treat this as an XY problem - please consider reading that after reading this answer.]
What you are missing is the separation of the data from the presentation of the data.
It is not a good idea to use controls to store data: they are meant to show the underlying data.
You could use two arrays for the data, one for the year and one for the population count, or you could use a Class which has properties of the year and the count. The latter is more sensible, as it ties the year and count together in one entity. You can then have a List of that Class to make a collection of the data, like this:
Option Infer On
Option Strict On
Imports System.IO
Public Class Form1
Public Class PopulationDatum
Property Year As Integer
Property Count As Integer
End Class
Function GetData(srcFile As String) As List(Of PopulationDatum)
Dim data As New List(Of PopulationDatum)
Using sr As New StreamReader(srcFile)
While Not sr.EndOfStream
Dim thisLine = sr.ReadLine
Dim parts = thisLine.Split(","c)
If parts.Count = 2 Then
data.Add(New PopulationDatum With {.Year = CInt(parts(0).Trim()), .Count = CInt(parts(1).Trim)})
End If
End While
End Using
Return data
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim srcFile = "C:\temp\PopulationData.txt"
Dim popData = GetData(srcFile)
Dim popTotal = 0
For Each p In popData
lstYear.Items.Add(p.Year)
lstPop.Items.Add(p.Count)
popTotal = popTotal + p.Count
Next
' popTotal now has the value of the sum of the populations
End Sub
End Class
If using a List(Of T) is too much, then just use the idea of separating the data from the user interface. It makes processing the data much simpler.
I am using 3 unbound DataGridView controls to display certain information. To load the information into those DGVs, I am pulling the information from an encrypted file, decrypting it, parsing the information, then trying to fill the DGVs with that information. The loading from the file is called by the menu item click. Here is what I have so far:
Private Sub miCLoad_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)
Handles miCLoad.Click
Dim FilePath As String = "C:\FList\CList.clt"
Dim LoadFile As New SaveandLoad.SaveAndLoad
Dim FileRead As New Simple3Des("MyPassword")
Dim FileString As String = FileRead.ReadFile(FilePath)
With LoadFile
.WhichList = dgCourses
.FilePath = FilePath
.DecryptedString = FileRead.DecryptData(FileString)
.dgList = dgCourses
End With
Call LoadFile.LoadFile()
End Sub
Public Class SaveandLoad
Public Property WhichList As New DataGridView
Public Property FilePath As String
Public Property DecryptedString As String
Public Property EncryptedString As String
Public Property dgList As Control
Public Sub LoadFile()
Dim dgRow As DataGridViewRow
Dim dgCell As DataGridViewTextBoxCell
Dim Lines() As String = DecryptedString.Split(vbLf)
Dim LinesList As List(Of String) = Lines.ToList
LinesList.RemoveAt(Lines.Length - 1)
For Each Line As String In LinesList
Dim Fields() As String = Line.Split(",")
dgRow = New DataGridViewRow
For x = 0 To (WhichList.Columns.Count - 1) Step 1
dgCell = New DataGridViewTextBoxCell
dgCell.Value = Fields(x).ToString
dgRow.Cells.Add(dgCell)
Next
WhichList.Rows.Add(dgRow)
Next
Select Case WhichList.Name
Case "dgCourses"
frmFacultyList.dgCourses = WhichList
frmFacultyList.dgCourses.Refresh()
WhichList.Dispose()
Case "dgFList"
frmFacultyList.dgFList = WhichList
frmFacultyList.dgFList.Refresh()
WhichList.Dispose()
Case "dgSList"
frmFacultyList.dgSList = WhichList
frmFacultyList.dgSList.Refresh()
WhichList.Dispose()
End Select
MsgBox("List Successfully Loaded", vbOKOnly, "Load")
End Sub
I want to be able to reference (or fill) a DGV without using 'select case' or 'if-then' statements. This will be too inefficient once I start adding the many other DGVs, that will be added in the future. Therefore, the title is the main question. I am using VS Express 2010.
I don't know VB too much, however, I'll post my solution in C# (may be helpfull in some way....)
DataGridView myDGV;
foreach (var item in this.Controls)
{
if (item.GetType() == typeof(DataGridView))
{
if (((DataGridView)item).Name == WhichList.Name)
{
//Cannot assing to 'item' here, because it is a 'foreach iteration variable'
//However you can save the variable for later use.
myDGV = (DataGridView)item;
}
}
}
myDGV = WhichList;
// different approach
DataGridView myDGV = (DataGridView)this.Controls.Find(WhichList.Name, false).First();
myDGV = WhichList;
Here is what worked for me in VB.NET:
Dim FormControls As New frmFacultyList.ControlCollection(frmFacultyList)
For Each DGV As DataGridView In FormControls
If WhichList.Name = DGV.Name Then
DGV = WhichList
DGV.Refresh()
End If
Next
Make an instance of the control collection then search specifically for DGVs using For Each. Simple and efficient.
I'm working on a project that requires me to take values from a CSV file. I've to do further processing with these values and it'd be great if I can have these values in a 2D array. The number of rows and columns of the CSV files changes at regular intervals.
I'm unable to take these values into a 2D array in VB.NET/C#. Can I please have some help on that?
Here the code that I used:
Imports System.IO
Public Class Form1
Private Sub ReadCSVFileToArray()
Dim strfilename As String
Dim num_rows As Long
Dim num_cols As Long
Dim x As Integer
Dim y As Integer
Dim strarray(1, 1) As String
' Load the file.
strfilename = "test.csv"
'Check if file exist
If File.Exists(strfilename) Then
Dim tmpstream As StreamReader = File.OpenText(strfilename)
Dim strlines() As String
Dim strline() As String
strlines = tmpstream.ReadToEnd().Split(Environment.NewLine)
' Redimension the array.
num_rows = UBound(strlines)
strline = strlines(0).Split(",")
num_cols = UBound(strline)
ReDim strarray(num_rows, num_cols)
' Copy the data into the array.
For x = 0 To num_rows
strline = strlines(x).Split(",")
For y = 0 To num_cols
strarray(x, y) = strline(y)
Next
Next
' Display the data in textbox
For x = 0 To num_rows
For y = 0 To num_cols
TextBox1.Text = TextBox1.Text & strarray(x, y) & ","
Next
TextBox1.Text = TextBox1.Text & Environment.NewLine
Next
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
ReadCSVFileToArray()
End Sub
End Class
You use the CsvReader from here to easily and conveniently read a csv (or other similar data) into a DataTable (or an IDataReader). It is always my first choice for this scenario - fast and pretty robust.
I'm not disagreeing with Marc Gravell, because you shouldn't try to re-invent the wheel. His solution would perform the best, and there can be many CSV format nuances that can screw up a simple parser such as the one demonstrated below.
With that said, you asked for a CSV parser that returns a 2D array. The code below does just that (jagged array), and should work for a very simple CSV file. This skips a header row in the file.
(sorry it's not in VB, but you can put it in a helper class in your project)
private string[][] GetData(string fileName)
{
List<string[]> data = new List<string[]>();
using (StreamReader sr = new StreamReader(fileName))
{
bool headerRow = true;
string line;
while ((line = sr.ReadLine()) != null)
{
if (headerRow)
{
headerRow = false;
}
else
{
string[] split = line.Split(new char[] { ',' });
data.Add(split);
}
}
}
return data.ToArray();
}
Parsing a CSV file can be pretty hard due to all the possible variations of CSV. See: http://en.wikipedia.org/wiki/Comma-separated_values
E.g., think about embedded quotes, commas etc.
So it will not be a simple matter of reading lines/strings and splitting them.
Perhaps you are better of by using a third party library
Since the number of columns and rows change frequently, you could use dynamic lists rather than a fixed 2D array.
List<List<string>> data = new List<List<string>>();
Then as you parse the CSV file you can build up the data dynamically in the above structure, adding a new List<string> to data for each new row in the CSV file.
Update: The VB.NET equivalent is something like
data As New List(Of List(Of String))
That being said, #Mark Gravell's suggestion is a far better solution than doing this yourself.