Reading txt file line by line in DataGrid View - vb.net

I'm completely new to VB.net and have been given a homework assignment. I need to be able to read certain lines and display them in a DataGridView. I have been able to link my .txt file to the DGV however it reads the whole file as opposed to the specific line. I have 4 buttons: btn1, btn2, btn3, btn4. I want each button to show the respective lines in the text file. After researching on-line for the past week I'm still stuck. If anyone could help me I would really appreciate it.
Text File ("database.txt")
(Line1) c1 c2 c3
(Line2) one 1-1 1-2
(Line3) two 2-2 2-3
(Line4) three 3-2 3-3
(Line5) four 4-2 4-3
Public Class Form1
Private Sub btn1_Click(sender As Object, e As EventArgs) Handles btn1.Click
Dim lines = (From line In IO.File.ReadAllLines("database.txt") _
Select line.Split(CChar(vbTab))).ToArray
For x As Integer = 0 To lines(0).GetUpperBound(0)
DataGridView1.Columns.Add(lines(0)(x), lines(0)(x))
Next
For x As Integer = 1 To lines.GetUpperBound(0)
DataGridView1.Rows.Add(lines(x))
Next
End Sub
End Class

You did not describe the result you get, so I cannot tell what's wrong right now, as I am not on a computer and usually use c++/cli (visual c++). If I have to make a guess, I would say that you fill each file row into one DGV row, but you want just 1 row, not all. Correct?
Anyway, here are some suggestions:
Do you have to read the file on each button click? If not, I would read it once, store the content and fill into the DGV when desired.
Debug your code! Set a breakpoint at the start of the function, step over the code lines, check the variable content.
Split up the code line dim lines into 3 or more separate lines to make it more readable. Also helps at debugging.

I made following assumptions based on what you had in your question:
First line of the file was the column names
The (Line#) was just there for reference and isn't in the actual file
The number in the button name (btn#) was the row index that should be displayed in the DGV
I added a new button to the form that loads the data from the text file, parses it to a datatable and sets the DataGridView1.DataSource to that DataTable. The second method then creates a new datatable and imports the specified row from the main datatable and shows it in the DGV. Of course many will say you should use a DataView with a DataView.RowFilter for this rather than creating a new datatable, however this way works just the same.
Private txtDataTable As DataTable
Private Sub loadFileBtn_Click(sender As Object, e As EventArgs) Handles loadFileBtn.Click
txtDataTable = New DataTable("txtContents")
Dim txtContents As String()
Try
txtContents = IO.File.ReadAllLines("C:\StackOverflow\database.txt")
Catch ex As Exception
MsgBox(ex.Message)
Return
End Try
Dim txtLines As New List(Of String())
txtContents.ToList().ForEach(Sub(x) txtLines.Add(x.Split(CChar(vbTab))))
If txtLines.Count > 0 Then
txtLines.Item(0).ToList.ForEach(Sub(x) txtDataTable.Columns.Add(New DataColumn(x.ToString)))
txtLines.RemoveAt(0)
End If
If txtLines.Count > 0 Then
txtLines.ToList.ForEach(Sub(x) txtDataTable.Rows.Add(x.ToArray))
End If
DataGridView1.DataSource = txtDataTable
End Sub
Private Sub btn_Click(sender As Object, e As EventArgs) Handles btn1.Click, btn2.Click, btn3.Click, btn4.Click
If txtDataTable Is Nothing Then Return
Dim rowIndex As Integer
If Integer.TryParse(DirectCast(sender, Button).Name.Replace("btn", String.Empty), rowIndex) Then
rowIndex -= 1
Else
Return
End If
Dim TempTable As DataTable = txtDataTable.Clone
If rowIndex < txtDataTable.Rows.Count Then
TempTable.ImportRow(txtDataTable.Rows(rowIndex))
End If
DataGridView1.DataSource = TempTable
End Sub

Related

Form Loads slowly in vb.net

I am currently working in ERP project on vb.net. I want to load product data in a textbox on form load. I am using autocomplete method but having a data of around 26000 the form loads slowly for 4 mins. Is there any way to avoid this or is there any way to call this function in background when the application starts?
This is my autocomplete textbox code. It works fine but it hangs alot as of the data is so large.
Private Sub pn()
Try
con = Class1.dbconn
Dim dt As New DataTable
Dim ds As New DataSet
ds.Tables.Add(dt)
Dim da As New SqlDataAdapter("select [Part Name] from
Part_Master_Download$", con)
da.Fill(dt)
Dim r As DataRow
TextBox9.AutoCompleteCustomSource.Clear()
For Each r In dt.Rows
TextBox9.AutoCompleteCustomSource.Add(r.Item(0).ToString)
Next
con.Close()
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
the properties of the textbox should be set to true for autocompletetextbox
Don't populate the AutoCompleteCustomSource in a loop. Populate an array first and then load the list in one go with a single call to AddRange:
Dim items = dt.Rows.Cast(Of DataRow)().
Select(Function(row) CStr(row(0)).
ToArray()
TextBox9.AutoCompleteCustomSource.AddRange(items)
You should find that that speeds things up considerably. If there's still a problem with performance, we can look a bit further.
EDIT: To prove my point, I just tested the following code:
Public Class Form1
Private timer As Stopwatch
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
timer = Stopwatch.StartNew()
Dim rng As New Random
Dim a = Convert.ToInt32("a"c)
Dim z = Convert.ToInt32("z"c)
Dim items = Enumerable.Range(1, 26000).Select(Function(n) Convert.ToChar(rng.Next(a, z + 1)).ToString())
For Each item In items
TextBox1.AutoCompleteCustomSource.Add(item)
Next
End Sub
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
MessageBox.Show(timer.Elapsed.ToString())
End Sub
End Class
and the message displayed "00:03:08.3167858", i.e. just over three minutes to load the list. I then changed the Load event handler to this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
timer = Stopwatch.StartNew()
Dim rng As New Random
Dim a = Convert.ToInt32("a"c)
Dim z = Convert.ToInt32("z"c)
Dim items = Enumerable.Range(1, 26000).Select(Function(n) Convert.ToChar(rng.Next(a, z + 1)).ToString())
TextBox1.AutoCompleteCustomSource.AddRange(items.ToArray())
End Sub
so a single call to AddRange instead of calling Add in a loop, and the message was "00:00:00.0557427", i.e. just under 56 milliseconds. Is that better?
You can use paging to control the amount of returned data. Check this link for detailed examples.
Another way is to use (Task Async and await) so you won't lock your UI.
Here is a few last suggestions not related directly to the question but might help clean the code a bit:
you can skip the dataset and use the datatable directly, you don't need it for just one table.
Bind your results in the datatable to a datagridview or a combobox instead of iterating through your results and filling a textbox.
cheers

DataGridView DataSource // integer to icon

I've got a list-based application that runs very slow, because each row in my DataGridView is build manually as DataGridViewRow. To fix performance issues i decided to use a DataSource (DataTable) instead.
My problem:
One column I am recieving from my database is filled with image IDs. The application knows which image belongs to which id. When creating gridrows manually there was no problem in translating int to a bitmap and use the bitmap as value for an imagecolumn. Now using the Datatable instead i cant get it working. For testing i wrote the following class:
Public Class test
Dim img As Bitmap
Public Sub New(image As Bitmap)
InitializeComponent()
Me.img = image
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim dt As New DataTable
dt.Columns.Add("StrCol")
dt.Columns.Add("ImgCol")
Dim row As DataRow = dt.NewRow
row.Item(0) = "Ohne Bild"
row.Item(1) = 0
Dim row2 As DataRow = dt.NewRow
row2.Item(0) = "Mit Bild"
row2.Item(1) = 1
dt.Rows.Add(row)
dt.Rows.Add(row2)
DataGridView1.DataSource = dt
End Sub
Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
If e.ColumnIndex = 1 Then
If e.Value = 0 Then
e.Value = Nothing
Else
e.Value = img
End If
End If
End Sub
End Class
hint:
"Ohne Bild" translates into "Without image"
"Mit Bild" translates into "With image"
Output after Button1_Click()
EDIT
Due to a missunderstanding I will try to clarify my question;
By list-based i dont actually mean the list-object. My bad there.By list-based i mean an application that displays the user Database entrys he made. The very basic of my application is simple: Allow user to add Rows, Edit and delete rows in a datagridview. This information is saved and edited in a database. Some of these informations in the rows (2 columns to be exactly) are displayed as icons while the application simply saved an id for the icon in the database. So the application knows which ID belongs to which icon.
The sample dt in the question is just to have something to work with. In the application it will be data recieved from a database to a datatable.
I finally solved it myself.
I get a datatable from my database.
Then I add a Column to it with the DataType Bitmap
Then I loop through each row of the DataTable and translate the Image_ID column to the image itself and put the image into the new Column
Then I remove the Image_ID column
Then I use the new DataTable as DataSource
I created that code in my main application which is a lot more complex so I can't show the code here. Sorry for that and thanks for your help.
It is important to do every change you need to do before using the table as source, because as soon as the gridView has to change something that is displayed it takes a lot more time.

Silverlight Datagrid New Row Goes to Bottom and Screws Up Multi-Delete Logic

I have a Silverlight 4 application I've been tasked with making some changes to. I'd rather redo it in something supported, but that's not an option at the moment. I have a datagrid and I've added a button to insert a new row into the datagrid and database table using the values from two comboboxes:
Private Sub btnAddAccountGroupLink_Click(sender As Object, e As System.Windows.RoutedEventArgs) Handles btnAddAccountGroupLink.Click
Dim sv_ag As Com_AccountGroup = cbACCT_GROUP.SelectedValue
Dim sv_cf As CS_FUND = cbACCT_CD.SelectedValue
AccountGroupLinkData.DataView.Add(New Com_AccountGroupLink() With {.ACCT_GROUP = sv_ag.ACCT_GROUP, .ACCT_CD = sv_cf.ACCT_CD})
AccountGroupLinkData.SubmitChanges()
End Sub
When the row is added, it gets added to the bottom of the datagrid instead of it's properly sorted location. This also breaks my code that deletes selected indexes from the datagrid:
Private Sub btnRemoveSelected_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles btnRemoveSelected.Click
If MessageBox.Show("Are you sure you want to delete the selected records?", "Confirmation", MessageBoxButton.OKCancel) = MessageBoxResult.OK Then
Dim indexesToDelete As New List(Of Integer)
For i As Integer = 0 To AccountGroupLinkData.DataView.Count - 1
If AccountGroupLinkData.DataView(i).IsSelected Then
indexesToDelete.Add(i)
End If
Next i
For d As Integer = 0 To indexesToDelete.Count - 1
AccountGroupLinkData.DataView.RemoveAt(0)
Next d
AccountGroupLinkData.SubmitChanges()
End If
End Sub
So if I delete the bottom row, it actually deletes the row that's located where this row should be.
How can I force the datagrid to update or show the row in its correct position?
I solved this by adding this code after the SubmitChanges:
NavigationService.Refresh()
This refreshes the Silverlight page and the row pops up to the proper position.

How to filter unbound datagridview with textbox vb.net?

I'm a complete rookie. I learned so much from here but this one I can't find the answer to. I'm using Visual Studio Pro 2015.
I have a windows form application that has a single column datagridview that is populated by reading a textfile, line by line at runtime. Each time the contents of the textfile will be different.
I want the user to be able to filter the list in the datagridview by entering characters in a textbox. The data is not "bound" to the datagridview, because at this point I don't know if that is necessary, and I don't completely understand it.
This is the code that I have for loading the datagridview, and the textbox is called txtFilter.
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'read all lines from the file into a string array (one line per string)
Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr)
Dim dgrow As DataGridViewRow
Dim dgcell As DataGridViewCell
'insert each line of input into a row in the datagrid
For Each line As String In lines
dgrow = New DataGridViewRow
dgcell = New DataGridViewTextBoxCell
If line <> "" Then
dgcell.Value = line
dgrow.Cells.Add(dgcell)
DataGridView1.Rows.Add(dgrow)
End If
Next
DataGridView1.Columns("ObjectName").ReadOnly = True
DataGridView1.ClearSelection()
End Sub
Edit:
Looking at your solution, I would advise you execute Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr) outside of the txtFilter_TextChanged sub, as otherwise you are importing the entire list every time the user enters a key, which is unnecessary. If the list may change while the user is using the program, I'd instead recommend adding a 'refresh' button, especially if your text file could be a long one.
You also added a couple lines of code to remove the sound when the user presses the Enter key. If the user is expected to press the enter key to search, then you don't need to update the DataGridView every time the user enters a new character in the textbox. This would be easier on memory, and again very beneficial if you have a large text file.
I'm sure there's an easier way, but here's my approach.
Private Sub txtFilter_TextChanged(sender As Object, e As EventArgs) Handles txtFilter.TextChanged
Dim searchedlines(-1) As String 'create an array to fill with terms that match our search
If txtFilter.Text = Nothing Then
datapopulate(lines) 'just populate the datagridview with our text file array
Else
For Each line In lines
If line Like "*" & txtFilter.Text & "*" Then 'check if anything in our search matches any of our terms
ReDim Preserve searchedlines(UBound(searchedlines) + 1) 'resize the array to fit our needs
searchedlines(UBound(searchedlines)) = line 'add the matched line to our array
End If
Next
datapopulate(searchedlines) 'populate the datagrid with our matched terms array
End If
End Sub
Private Sub datapopulate(ByVal mylist)
Dim dgrow As DataGridViewRow
Dim dgcell As DataGridViewCell
DataGridView1.Rows.Clear() 'clear the grid
For Each line As String In mylist 'do the same thing here that we did on form load
dgrow = New DataGridViewRow
dgcell = New DataGridViewTextBoxCell
dgcell.Value = line
dgrow.Cells.Add(dgcell)
DataGridView1.Rows.Add(dgrow)
Next
End Sub
What I did is created a sub that handles whenever the text in txtFilter is changed. Alternatively, you could run that code in a sub that handles a button click. Given that, from what I know, ReDim can be a costly item in terms of memory usage, if your text document was hundreds of lines long, you might want it on a button click instead. You could probably use a list, but I haven't played around enough to know how to go about doing that.
An important note: in order for the sub txtFilter_TextChanged to be able to see lines(), I defined it outside of a sub but inside of your main class, so that all subs could access it, like so:
Public Class Form1
Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr)
'...subs here...
End Class
I hope this helps you get started!
I have a solution working. Thank you very much.
Private Sub txtFilter_TextChanged(sender As Object, e As EventArgs) Handles txtFilter.TextChanged
'read all lines from the file into a string array (one line per string)
Dim lines() As String = My.Computer.FileSystem.ReadAllText("c:\list_in.txt").Replace(vbLf, "").Split(vbCr)
Dim dgrow As DataGridViewRow
Dim dgcell As DataGridViewCell
DataGridView1.Rows.Clear()
'insert each line of input into a row in the datagrid
For Each line As String In lines
dgrow = New DataGridViewRow
dgcell = New DataGridViewTextBoxCell
If line.Contains(txtFilter.Text) Then
dgcell.Value = line
dgrow.Cells.Add(dgcell)
DataGridView1.Rows.Add(dgrow)
End If
Next
DataGridView1.Columns("ObjectName").ReadOnly = True
DataGridView1.ClearSelection()
End Sub
And I also found this to eliminate the bell from ringing when the user pressed the enter key when entering the filter text.
Private Sub txtFilter_KeyPress(sender As Object, e As KeyPressEventArgs) Handles txtFilter.KeyPress
' this keeps the bell from ringing when the user presses the 'enter' key
If Asc(e.KeyChar) = 13 Then
e.Handled = True
End If
End Sub

Delete line of structure array and update it in tab vb.net

So I have a listbox, filled with informations from a structure tab as follows :
Private Sub Modifier_Load(sender As Object, e As EventArgs) Handles MyBase.Load
For i As Integer = 0 To frmConnecter.TabPolyFlix.Length - 1
ListBox1.Items.Add(frmConnecter.TabPolyFlix(i).strTitre)
Next
End Sub
And I want the user to choose to delete the TabPolyFlix(ListBox1.SelectedIndex) and it has to get updated in the original Tab and thus in the Listbox
P.S I tried this but it only updates it in the listbox, not in the original tab
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim Temp As New List(Of ListViewItem)
For i As Integer = 0 To ListBox1.Items.Count - 1
If ListBox1.SelectedIndices.Contains(i) = False Then
Temp.Add(ListBox1.Items(i))
End If
Next
ListBox1.Items.Clear()
For i As Integer = 0 To Temp.Count - 1
ListBox1.Items.Add(Temp(i))
Next i
End Sub
Arrays have a fixed size. Better use a List(Of ListViewItem) for TabPolyFlix. Then assign the List directly to the DataSource of the listbox:
'Fill the listbox
ListBox1.DisplayMember = "strTitre" 'You can also set this in the property grid.
ListBox1.DataSource = frmConnecter.TabPolyFlix
You can also override the ToString method of ListViewItem in order to display anything you want in the listbox and keep ListBox1.DisplayMember empty.
Now you can directly delete the item in the list:
frmConnecter.TabPolyFlix.Remove(ListBox1.SelectedItem)
or
frmConnecter.TabPolyFlix.RemoveAt(ListBox1.SelectedIndex)
or if you want to delete several items at once
For Each i As Integer In ListBox1.SelectedIndices.Cast(Of Integer)()
frmConnecter.TabPolyFlix.Remove(ListBox1.Items(i))
Next
and re-display the list:
ListBox1.DataSource = Nothing
ListBox1.DataSource = frmConnecter.TabPolyFlix
The moral of the story is: don't use controls (the ListBox in this case) as your primary data structure. Use it only for display and user interaction. Perform the logic (the so called business logic) on display-independent data structures and objects (so called business objects) and when finished, display the result.