I'm using the below code to read from an excel file and add certain columns into a listview. Once imported i'm then exporting them to a CSV (code not shown).
My problem is that the excel file is a till extract and it displays the data by transaction which results in thousands of lines. I would like to perform the excel equivalent of SUMIF based on the EPoS line and consolidate the info if thats possible?
Sample of data below...
Public Structure ExcelRows
Dim Unit As String
Dim Outlet As String
Dim EPoS As String
Dim Quantity As String
Dim Value As String
Dim DateSale As String
End Structure
Public ExcelRowList As List(Of ExcelRows) = New List(Of ExcelRows)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Public Function GetInfo() As Boolean
Dim Completed As Boolean = False
Dim MyExcel As New Excel.Application
Dim enUK As New CultureInfo("en-GB")
Dim DOS As String = "01/04/15"
MyExcel.Workbooks.Open("C:\Dropbox\Tills\taRunAction1.xlsx")
MyExcel.Sheets("Report").Activate()
MyExcel.Range("A10").Activate()
Dim ThisRow As New ExcelRows
Do
If MyExcel.ActiveCell.Value > Nothing Or MyExcel.ActiveCell.Text > Nothing Then
ThisRow.Unit = MyExcel.ActiveCell.Value
MyExcel.ActiveCell.Offset(0, 1).Activate()
ThisRow.Outlet = MyExcel.ActiveCell.Value
MyExcel.ActiveCell.Offset(0, 1).Activate()
ThisRow.DateSale = MyExcel.ActiveCell.Value
MyExcel.ActiveCell.Offset(0, 2).Activate()
ThisRow.EPoS = MyExcel.ActiveCell.Value
MyExcel.ActiveCell.Offset(0, 1).Activate()
ThisRow.Quantity = MyExcel.ActiveCell.Value
MyExcel.ActiveCell.Offset(0, 1).Activate()
ThisRow.Value = MyExcel.ActiveCell.Value
ExcelRowList.Add(ThisRow)
MyExcel.ActiveCell.Offset(1, -6).Activate()
Else
Completed = True
Exit Do
End If
Loop
MyExcel.Workbooks.Close()
MyExcel = Nothing
Return Completed
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
If GetInfo() = True Then
For Each xItem In ExcelRowList
Dim lViewItem As ListViewItem
lViewItem = ListView1.Items.Add(xItem.Unit)
lViewItem.SubItems.AddRange(New String() {xItem.Outlet, xItem.EPoS, xItem.Quantity, xItem.Value, xItem.DateSale})
Next
End If
End Sub
#Plutonix's answer is a good idea and I totally agree with his comments about changing your ExcelRows structure/class fields from strings to the appropriate numerical type.
Another possibility is to use LINQ to group the data from your ExcelRowList. Something like the following
Dim results = From r In ExcelRowList
Group By r.EPoS
Into Group, Sum(r.Value * r.Quantity)
Note that this won't work as is because r.Value and r.Quantity are strings so you can't multiply them. So you should change the field types in your structure and then when you are looping through the cells, cast the cell value to the correct type. You will have to decide what to do if the cast fails.
Group By on MSDN.
One way to do this would be to use a DataTable:
Private dt As DataTable
...
dt = New DataTable
dt.Columns.Add("EPOS", GetType(Integer))
dt.Columns.Add("Unit", GetType(String))
dt.Columns.Add("Quantity", GetType(Integer))
dt.Columns.Add("Value", GetType(Decimal))
dt.Columns.Add("Total", GetType(Decimal))
dt.Columns.Add("Date", GetType(DateTime))
Dim keys As DataColumn() = {dt.Columns("EPOS")}
dt.PrimaryKey = keys
It is not clear whether "VALUE" is the Unit Price or Sale Amount because all the units are 1. I am not sure I would repeat data such as the SaleDate over and over, The key is that the DataTable could replace the XLRows Structure.
One key is that you want to work with properly Typed data so you can multiply and add. The XLRow structure is not properly typed - everything is string rather than Date, Decimal, Integer etc.
A. XLRow Class to Convert
For this, convert the XLRow structure to a class. Its role would be to take in string data from XL and convert to Typed data. Next, the DataTable is used to collect the rollup data.
For this, the PrimaryKey definition above is critical. It will prevent you from adding a second (or 1000th) "Pepsi" item and allow you to find that summary item. Perhaps best of all, you can get rid of the ListView and use a DataGridView:
dgv.DataSource = dt
With one line of code, the DGV will create the columns and display all the rows in the DataTable. Unlike a ListView it will update itself as the underlying data in the DataTable changes. To iterate your data to summarize inside a loop:
' get the row for this EPOS code
Dim dr As DataRow = dt.Rows.Find(xl.EPOS)
If dr IsNot Nothing Then
' we already have this item, increment Quan, TotalSales:
dr("Quantity") += xl.Quantity
dr("Total") += (xl.Quantity * xl.Value)
Else
' new transaction item, add it:
dt.Rows.Add(xl.EPOS, xl.Unit, xl.Quantity,
xl.Value, (xl.Quantity * xl.Value), xl.DateSale)
End If
This is pretty concise, because as you read data from XL, it is added to the Summary. There is no need to import all the detail data (XLS rows) into a collection or datatable before you perform the rollup.
B. Use Linq instead of the XLRow structure
For this omit the Primary Key in the DataTable. In this case, the DataTable would collect the raw XLS data instead of the XLRow structure. Do convert to numerics for the rollup. Next, use linq to summarize the data; perhaps into another DataTable. Example:
Sample Data:
dt.Rows.Add(10001, "Ginger Ale", 1, 2.25, #4/5/2015#)
dt.Rows.Add(34582, "Pepsi", 3, 6.0, #4/5/2015#)
dt.Rows.Add(10002, "Chips", 1, 3.25, #4/5/2015#)
dt.Rows.Add(34582, "Pepsi", 1, 2.0, #4/5/2015#)
dt.Rows.Add(78301, "Roast Duck", 1, 15.25, #4/5/2015#)
dt.Rows.Add(34582, "Pepsi", 1, 2.0, #4/5/2015#)
dt.Rows.Add(34582, "Pepsi", 1, 2.0, #4/5/2015#)
dt.Rows.Add(10002, "Chips", 1, 3.25, #4/5/2015#)
dt.Rows.Add(34582, "Pepsi", 1, 2.0, #4/5/2015#)
To get the summary info:
' group the data by EPOS code
Dim drs = From row In dt.AsEnumerable()
Group row By ID = row.Field(Of Integer)("EPOS") Into Group
Select Group
Dim TotSales As Decimal
Dim TotUnits As Integer
' each DRS is a collection of all the items with the same EPOS code
Dim dr As DataRow()
Console.WriteLine("EPOS Item Lines Units Total Sales")
' get the total sales in each group
For n As Integer = 0 To drs.Count - 1
dr = drs(n) ' the current EPOC group
TotUnits = dr.Sum(Function(t) t.Field(Of Integer)("Quantity"))
' Sales could just be TotUnits * dr(0)("Value")
' sample data makes it unclear if Value is the UNITPRICE or SALEAMOUNT
' This assumes it is SALEAMOUNT such that 2 Pepsi = 4.00
TotSales = dr.Sum(Function(t) t.Field(Of Decimal)("Value"))
' ToDo: do something interesting with the totals
Console.WriteLine("{0} {1} {2} {3} {4}",
dr(0)("EPOS"),
dr(0)("Unit").ToString,
dr.Length.ToString("D2"),
TotUnits.ToString,
TotSales.ToString("C2"))
Next
The output:
EPOS Item Lines Units Total Sales
10001 Ginger Ale 01 1 $2.25
34582 Pepsi 05 7 $14.00
10002 Chips 02 2 $6.50
78301 Roast Duck 01 1 $15.25
The key is that it does act much like SUMIF once you have the Typed data in a useful structure. The result shows that the data has 5 Pepsi entries, 7 total units and 7*2 = 14.
I think the loop version is slightly easier to manage and debug, and it much more economical since the Summary is built on the fly as XLS rows are read in.
Related
I have a Part class with the fields list in the code below. I have a DataGridView control, which I am filtering with the Advanced DGV (ADGV) DLL from NUGET. I must include the ADGV in my winform. I currently have a DataGridView, a search box on the form, and a button to run the following function. I need to go through all of the visible rows, collect a unique list of part numbers with their most recent revisions, and then color the rows in DataGridView which are out of date by checking the part number and rev on each row against the mostuptodate list. For 45,000 entries displayed in DataGridView, this take ~17 secs. For ~50 entries, it take ~1.2 seconds. This is extremely inefficient, but I can't see a way to cut the time down.
Sub highlightOutdatedParts()
'Purpose: use the results in the datagridview control, find the most recent revision of each part, and
' highlight all outdated parts relative to their respective most recent revisions
'SORT BY PART NUMBER AND THEN BY REV
If resultsGrid.ColumnCount = 0 Or resultsGrid.RowCount = 0 Then Exit Sub
Dim stopwatch As New Stopwatch
stopwatch.Start()
resultsGrid.Sort(resultsGrid.Columns("PartNumber"), ListSortDirection.Ascending)
Dim iBag As New ConcurrentBag(Of Part)
Dim sortedList As Generic.List(Of Part)
For Each row As DataGridViewRow In resultsGrid.Rows
If row.Visible = True Then
Dim iPart As New Part()
Try
iPart.Row = row.Cells(0).Value
iPart.Workbook = CStr(row.Cells(1).Value)
iPart.Worksheet = CStr(row.Cells(2).Value)
iPart.Product = CStr(row.Cells(3).Value)
iPart.PartNumber = CStr(row.Cells(4).Value)
iPart.ItemNo = CStr(row.Cells(5).Value)
iPart.Rev = CStr(row.Cells(6).Value)
iPart.Description = CStr(row.Cells(7).Value)
iPart.Units = CStr(row.Cells(8).Value)
iPart.Type = CStr(row.Cells(9).Value)
iPart.PurchCtgy = CStr(row.Cells(10).Value)
iPart.Qty = CDbl(row.Cells(11).Value)
iPart.TtlPerProd = CDbl(row.Cells(12).Value)
iPart.Hierarchy = CStr(row.Cells(13).Value)
iBag.Add(iPart)
Catch ice As InvalidCastException
Catch nre As NullReferenceException
End Try
End If
Next
sortedList = (From c In iBag Order By c.PartNumber, c.Rev).ToList() ' sort and convert to list
Dim mostUTDRevList As New Generic.List(Of Part) ' list of most up to date parts, by Rev letter
For sl As Integer = sortedList.Count - 1 To 0 Step -1 'start at end of list and work to beginning
Dim query = From entry In mostUTDRevList ' check if part number already exists in most up to date list
Where entry.PartNumber = sortedList(sl).PartNumber
Select entry
If query.Count = 0 Then ' if this part does not already exist in the list, add.
mostUTDRevList.Add(sortedList(sl))
End If
Next
'HIGHLIGHT DATAGRIDVIEW ROWS WHERE PART NUMBERS ARE OUT OF DATE
For Each row As DataGridViewRow In resultsGrid.Rows
' if that part with that Rev does not exist in the list, it must be out of date
Try
Dim rowPN As String = CStr(row.Cells(4).Value).ToUpper ' get part number
Dim rowR As String = CStr(row.Cells(6).Value).ToUpper ' get Rev
Dim query = From entry In mostUTDRevList ' check if that part number with that Rev is in the list.
Where entry.PartNumber.ToUpper.Equals(rowPN) AndAlso
entry.Rev.ToUpper.Equals(rowR)
Select entry
If query.Count = 0 Then ' if the part is out of date highlight its' row
row.DefaultCellStyle.BackColor = Color.Chocolate
End If
Catch ex As NullReferenceException
Catch ice As InvalidCastException
End Try
Next
resultsGrid.Select()
stopwatch.Stop()
If Not BackgroundWorker1.IsBusy() Then timertextbox.Text = stopwatch.Elapsed.TotalSeconds.ToString & " secs"
MessageBox.Show("Highlighting completed successfully.")
End Sub
It is almost always faster to work with the data than the control. The control is simply the means to present a view of the data (in a grid) to the users. Working with the data from there requires too much converting to be effieicent. Then, use the DGV events to highlight the rows
Its hard to tell all the details of what you are doing, but it looks like you are comparing the data to itself (as opposed to some concrete table where the lastest revision codes are defined). Nor is it clear why the datasources are collections, ConcurrentBags etc. The key would be to use collections optimized for the job.
To demonstrate, I have a table with 75,000 rows; the product codes are randomly selected from a pool of 25,000 and a revision code is a random integer (1-9). After the DGV datasource is built (a DataTable) a LookUp is created from the ProductCode-Revision pair. This is done once and once only:
' form level declaration
Private PRCodes As ILookup(Of String, Int32)
' go thru table
' group by the product code
' create an anon Name-Value object for each,
' storing the code and highest rev number
' convert result to a LookUp
PRCodes = dtSample.AsEnumerable.
GroupBy(Function(g) g.Item("ProductCode"),
Function(key, values) New With {.Name = key.ToString(), .Value = values.
Max(Of Int32)(Function(j) j.Field(Of Int32)("RevCode"))
}).
ToLookup(Of String, Int32)(Function(k) k.Name, Function(v) v.Value)
Elapsed time via stopwatch: 81 milliseconds to create the collection of 23731 items. The code uses an anonymous type to store a Max Revision code for each product code. A concrete class could also be used. If you're worried about mixed casing, use .ToLowerInvariant() when creating the LookUp (not ToUpper -- see What's Wrong With Turkey?) and again later when looking up the max rev.
Then rather than looping thru the DGV rows use the RowPrePaint event:
If e.RowIndex = -1 Then Return
If dgv1.Rows(e.RowIndex).IsNewRow Then Return
' .ToLowerInvariant() if the casing can vary row to row
Dim pc = dgv1.Rows(e.RowIndex).Cells("ProductCode").Value.ToString()
Dim rv = Convert.ToInt32(dgv1.Rows(e.RowIndex).Cells("RevCode").Value)
Dim item = PRCodes(pc)(0)
If item > rv Then
dgv1.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.MistyRose
End If
Notes
It takes some time to create the DataSource, but 75,000 rows is a lot to throw at a user
The time to create the LookUp is minimal - barely measurable
There is no noticeable wait in displaying them because a) the LookUp is made for this sort of thing, b) rows are done as needed when they are displayed. Row # 19,999 may never be processed if the user never scrolls that far.
This is all geared to just color a row. If you needed to save the Current/NotCurrent state for each row, add a Boolean column to the DataTable and loop on that. The column can be invisible if to hide it from the user.
The random data results in 47,000 out of date RevCodes. Processing 75k rows in the DataTable to set the flag takes 591 milliseconds. You would want to do this before you set the DataTable as the DataSource to prevent changes to the data resulting in various events in the control.
In general, the time to harvest the max RevCode flag and even tag the out of date rows is a trivial increment to creating the datasource.
The Result:
The data view is sorted by ProductCode so that the coloring of lower RevCodes is apparent.
We surely cant grok all the details and constraints of the system from a small snippet - even the data types and original datasource are a guess for us. However, this should provide some help with better look-up methods, and the concept of working with the data rather than the user's view.
One thing is the revision code - yours is treating them as a string. If this is alphanumeric, it may well not compare correctly - "9" sorts/compares higher than "834" or "1JW".
See also:
Lookup(Of TKey, TElement) Class
Anonymous Types
The solution was spurred in part by #Plutonix.
Sub highlightOutdatedParts()
If resultsGrid.ColumnCount = 0 Or resultsGrid.RowCount = 0 Then Exit Sub
Dim stopwatch As New Stopwatch
stopwatch.Start()
resultsGrid.DataSource.DefaultView.Sort = "PartNumber ASC, Rev DESC"
resultsGrid.Update()
'HIGHLIGHT DATAGRIDVIEW ROWS WHERE PART NUMBERS ARE OUT OF DATE
Dim irow As Long = 0
Do While irow <= resultsGrid.RowCount - 2
' if that part with that Rev does not exist in the list, it must be out of date
Dim utdPN As String = resultsGrid.Rows(irow).Cells(4).Value.ToString().ToUpper()
Dim utdRev As String = resultsGrid.Rows(irow).Cells(6).Value.ToString().ToUpper()
Dim iirow As Long = irow + 1
'If iirow > resultsGrid.RowCount - 1 Then Exit Do
Dim activePN As String = Nothing
Dim activeRev As String = Nothing
Try
activePN = resultsGrid.Rows(iirow).Cells(4).Value.ToString().ToUpper()
activeRev = resultsGrid.Rows(iirow).Cells(6).Value.ToString().ToUpper()
Catch ex As NullReferenceException
End Try
Do While activePN = utdPN
If iirow > resultsGrid.RowCount - 1 Then Exit Do
If activeRev <> utdRev Then
resultsGrid.Rows(iirow).DefaultCellStyle.BackColor = Color.Chocolate
End If
iirow += 1
Try
activePN = resultsGrid.Rows(iirow).Cells(4).Value.ToString().ToUpper()
activeRev = resultsGrid.Rows(iirow).Cells(6).Value.ToString().ToUpper()
Catch nre As NullReferenceException
Catch aoore As ArgumentOutOfRangeException
End Try
Loop
irow = iirow
Loop
resultsGrid.Select()
stopwatch.Stop()
If Not BackgroundWorker1.IsBusy() Then
timertextbox.Text = stopwatch.Elapsed.TotalSeconds.ToString & " secs"
resultcounttextbox.Text = resultsGrid.RowCount - 1 & " results"
End If
MessageBox.Show("Highlighting completed successfully.")
End Sub
My DataGridView's DataSource is bound to a DataView. The DataView is equal to my dtBills DataTable. Like so:
Dim View As New DataView
View.Table = DataSet1.Tables("dtBills")
dgvBills.DataSource = View
I have multiple columns in this DataGridView. One in particular has strings and integers as information. When I click on the DataGridView Column Header to sort the column, it sorts as strings like the column on the left:
'Curr Col >>> ' Wanted Result
10001 >>> 10001
100012 >>> 11000
11000 >>> 12000
110049 >>> 100012
12000 >>> 110049
E-1234 >>> E-1234
T-12345 >>> T-1235
T-1235 >>> T-12345
How would I go about sorting a bound DataGridView Column when pressing on the Column Header as I normally would? Should I use my DataView to help me out?
When a DataGridView is databound it is not possible to use its sorting and it is necessary to sort source data. The sorting is a bit complicated so I need two helper columns.
dgvBills.AutoGenerateColumns = False
tbl.Columns.Add(New DataColumn("Scol1", GetType(String)))
tbl.Columns.Add(New DataColumn("Scol2", GetType(Integer)))
The first one will contain a leading letters (or empty string). The second will contain only a number contained in the string. We will sort by Scol1, Scol2.
Now we set all comumns to Programatic mode (DataGridViewColumnSortMode Enumeration)
For Each column As DataGridViewColumn In dgvBills.Columns
column.SortMode = DataGridViewColumnSortMode.Programmatic
Next
And custom sorting is achieved in a handler of ColumnHeaderMouseClick (DataGridView.Sort Method (IComparer)). We will use sorting of the underlying view instead of the Grid sorting.
Private Sub dgvBills_ColumnHeaderMouseClick(sender As Object, e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles dgvBills.ColumnHeaderMouseClick
Dim newColumn As DataGridViewColumn = dgvBills.Columns(e.ColumnIndex)
Dim direction As ListSortDirection
Dim Modifier As String = ""
If newColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending Then
direction = ListSortDirection.Descending
Modifier = " desc"
Else
direction = ListSortDirection.Ascending
End If
Dim View As DataView = dgvBills.DataSource
If {"JobNumber", "JobNumber1"}.Contains(dgvBills.Columns(e.ColumnIndex).Name) Then
View.Table.Columns("Scol2").Expression = String.Format("Convert(iif (substring({0},1,2) like '*-',substring({0},3,len({0})-1),{0}), 'System.Int32')", dgvBills.Columns(e.ColumnIndex).Name)
View.Table.Columns("Scol1").Expression = String.Format("iif (substring({0},1,2) like '*-',substring({0},1,2),'')", dgvBills.Columns(e.ColumnIndex).Name)
View.Sort = String.Format("Scol1 {0},Scol2 {0}", Modifier)
Else
dgvBills.Sort(newColumn, direction)
End If
If direction = ListSortDirection.Ascending Then
newColumn.HeaderCell.SortGlyphDirection = SortOrder.Ascending
Else
newColumn.HeaderCell.SortGlyphDirection = SortOrder.Descending
End If
End Sub
In {"JobNumber", "JobNumber1"}.Contains ... is possible to set columns which are sorted differntly. Other columns are sorted as the Grid sorts them by default or it is possible to create another custom sorting.
Note: I have fully working example but I hope that fragments are good enough.
The column is sorted correctly as strings and I suppose you want to sort it as numbers. The problem is that it seems that the strings you have combine numbers and characters. The result is necessity rather complex sorting for a DataView.
Dim tbl As New DataTable("dtBills")
Dim DataSet1 As New DataSet
DataSet1.Tables.Add(tbl)
tbl.Columns.Add(New DataColumn("MyCol", GetType(String)))
Dim vals As String() = {"10001", "100012", "11000", "110049", "12000", "E-1234", "T-12345", "T-1235"}
For qq = 0 To vals.Length - 1
Dim row As DataRow
row = tbl.NewRow
row(0) = vals(qq)
tbl.Rows.Add(vals(qq))
Next
tbl = DataSet1.Tables("dtBills")
tbl.Columns.Add(New DataColumn("Scol2", GetType(Integer)) With {.Expression = "Convert(iif (substring(MyCol,1,2) like '*-',substring(MyCol,3,len(MyCol)-1),MyCol), 'System.Int32')"})
tbl.Columns.Add(New DataColumn("Scol1", GetType(String)) With {.Expression = "iif (substring(MyCol,1,2) like '*-',substring(MyCol,1,2),'')"})
Dim View As New DataView(tbl)
View.Sort = "Scol1,Scol2"
View.Table = DataSet1.Tables("dtBills")
So two new columns are added. The first maintain sorting by initial letters the second to enable sorting by the number contained in the a string.
hi this is my code to get datable from query but i need to check for some values like if there is 0 in any column then replace it with N.A.
Dim op As New cad
Dim dt As DataTable = op.Fetchbooks().Tables(0)
Dim sb As New StringBuilder()
Dim rp As System.Data.DataRow
If dt.Rows.Count > 0 Then
ListView1.DataSource = dt
ListView1.DataBind()
DropDownList1.DataSource = dt
DropDownList1.DataTextField = "Description"
DropDownList1.DataValueField = "Description"
DropDownList1.DataBind()
End if
so is there any way to check some values and edit in datable before binding ???
First, if possible i would modify your sql-query in Fetchbooks instead to replace 0 with N.A.. I assume you are querying the database.
However, if you want to do it in memory:
For Each row As DataRow In dt.Rows
For Each col As DataColumn In dt.Columns
If row.IsNull(col) OrElse row(col).ToString = "0" Then
row(col) = "N.A."
End If
Next
Next
Sure: you can iterate through the table, making whatever value replacements are appropriate (assuming that your replacement values are type-compatible with what came from the query).
Something like:
For Each row as DataRow in dt.Rows
if row("columnname") = "left" then
row("columnname") = "right"
Next
Then bind it to the grid, and you should see your updated values.
Once you have the DataTable object you can manipulate it as you need:
For Each rp In dt.Select("COLUMN_A = '0'")
rp("COLUMN_A") = "N.A."
Next
Note that this assumes that COLUMN_A is defined as a string type, not a numeric.
The challenge will be if you intend on saving this data back to it's source and you don't want the original 0 value to be saved instead of N.A.. You could add dt.AcceptChanges immediately after the above loop so that it will appear as the Fetchbooks query had these values all along.
I have a table named users which has the following columns in it
User_id,user_name,user_pwd,First_Name,Middle_Name,Last_Name and user_type.
I have dataset named dst and created a table called user in the dataset. Now I want to populate listbox with user_Name, First_Name, Last_name of each and every row in the table user.
I am able to add one column value at a time but not getting how to add multiple column values of each row to listbox
Dim dt As DataTable = Dst.Tables("user")
For Each row As DataRow In dt.Rows
lstUsers.Items.Add(row("User_Name"))
Next
Above code works perfectly but I also want to add First_name as well as last_name to the list box at the same time.
Use same approach as you have, but put all values you want in one string.
Dim dt As DataTable = Dst.Tables("user")
For Each row As DataRow In dt.Rows
Dim sItemTemp as String
sItemTemp = String.Format("{0},{1},{2}", row("User_Name"), row("First_Name"), row("Last_Name"))
lstUsers.Items.Add(sItemTemp)
Next
String.Format() function will call .ToString() on all parameters.
In this case if row(ColumnName) is NULL value then .ToString() return just empty string
You have 2 choices:
Using the ListBox:
To use the ListBox, set the font to one that is fixed width like courier new (so that the columns line up), and add the items like this:
For Each row As DataRow In dt.Rows
lstUsers.Items.Add(RPAD(row("User_Name"),16) & RPAD(row("First_Name"),16) & RPAD(row("Last_Name"),16))
Next
The RPAD function is defined like this:
Function RPAD(a As Object, LENGTH As Object) As String
Dim X As Object
X = Len(a)
If (X >= LENGTH) Then
RPAD = a : Exit Function
End If
RPAD = a & Space(LENGTH - X)
End Function
Adjust the LENGTH argument as desired in your case. Add one more for at least one space. This solution is less than ideal because you have to hard-code the column widths.
Use a DataGridView control instead of a ListBox. This is really the best option, and if you need, you can even have it behave like a ListBox by setting the option to select the full row and setting CellBorderStyle to SingleHorizontal. Define the columns in the designer, but no need to set the widths - the columns can auto-size, and I set that option in the code below. if you still prefer to set the widths, comment out the AutoSizeColumnsMode line.
The code to set up the grid and add the rows goes like this:
g.Rows.Clear() ' some of the below options are also cleared, so we set them again
g.AutoSizeColumnsMode = DataGridViewAutoSizeColumnMode.AllCells
g.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal
g.SelectionMode = DataGridViewSelectionMode.FullRowSelect
g.AllowUserToAddRows = False
g.AllowUserToDeleteRows = False
g.AllowUserToOrderColumns = True
For Each row As DataRow In dt.Rows
g.Rows.Add(row("User_Name"), row("First_Name"), row("Last_Name"))
Next
You might solved your problem by now but other users like me might have issue with it.
Above answers given worked for me even but I found a same answer in a simple way according to what I want..
cmd = New SqlCommand("select User_Name, First_Name, Last_Name from User")
Dim dr As SqlDataReader = cmd.ExecuteReader(YourConnectionString)
If dr.HasRows Then
Do While dr.Read
lst.Items.Add(dr.Item(0).ToString & " " & dr.Item(1).ToString & " " & dr.Item(2).ToString)
Loop
End If
This worked for me, maybe wrong way but I found it simple :)
May I suggest you use a ListView control instead of Listbox?
If you make the switch, here's a sample subroutine you could use to fill it up with the data you said you want. Adapt it the way you like; there's much room for improvement but you get the general idea:
Public Sub FillUserListView(lstUsers As ListView, Dst As DataSet)
Dim columnsWanted As List(Of String) = New List(Of String)({"User_Name", "First_Name", "Last_Name"})
Dim dt As DataTable = Dst.Tables("user")
Dim columns As Integer = 0
Dim totalColumns = 0
Dim rows As Integer = dt.Rows.Count
'Set the column titles
For Each column As DataColumn In dt.Columns
If columnsWanted.Contains(column.ColumnName) Then
lstUsers.Columns.Add(column.ColumnName)
columns = columns + 1
End If
totalColumns = totalColumns + 1
Next
Dim rowObjects(columns - 1) As ListViewItem
Dim actualColumn As Integer = 0
'Load up the rows of actual data into the ListView
For row = 0 To rows - 1
For column = 0 To totalColumns - 1
If columnsWanted.Contains(dt.Columns(column).ColumnName) Then
If actualColumn = 0 Then
rowObjects(row) = New ListViewItem()
rowObjects(row).SubItems(actualColumn).Text = dt.Rows(row).Item(actualColumn)
Else
rowObjects(row).SubItems.Add(dt.Rows(row).Item(actualColumn))
End If
lstUsers.Columns.Item(actualColumn).Width = -2 'Set auto-width
actualColumn = actualColumn + 1
End If
Next
lstUsers.Items.Add(rowObjects(row))
Next
lstUsers.View = View.Details 'Causes each item to appear on a separate line arranged in columns
End Sub
I created a custom GridView (actually a RadGrid) which does roughly the following.
Initialize GridView formatting
Setup Columns (depending on type of data to be displayed)
Populate the rows
When Instantiating this GridView, and adding it to my page, I want the first column to contain a "rowspan" attribute on the first row of repeated, but similar data. The "rowspan" value should be equal to the number of similar rows following it. In this way I hope to make the final view cleaner.
The logic for this, I figure, should take place while populating the rows. Initially I add rows to a DataTable and then bind it to the GridView as the final step.
Here's the general logic I was attempting, but it didn't work for me. What am I doing wrong?
Dim dt As New DataTable() 'Has three default columns
For Each d In Documents 'MAIN ITEM (First Column Data)
Dim rowspan As Integer
rowspan = 0
For Each f In Files
If rowspan = 0 Then
Me.dt.Rows.Add(New Object() {d.Title, f.Language, f.FileSize})
'THIS DOESN'T WORK!
Me.dt.Columns(0).ExtendedProperties.Item("rowspan") = rowspan.ToString()
Else
Me.dt.Rows.Add(New Object() {Nothing, f.Language, f.FileSize})
End If
rowspan += 1
Next
Next
Also keep in mind that the this is dumped into a DataView which is sorted by the first column, so I would imagine it must actually be sorted first, then somehow count the rows for each "like" first column, then set the "rowspan" value for the first row of that column equal to the number of rows belonging to it.
Does this make sense? Here is an ideal example of what I would like to accomplish in the layout:
Try this.
Protected Sub DemoGrid_PreRender(sender As Object, e As System.EventArgs) Handles DemoGrid.PreRender
MergeRowsWithSameContent(sender)
End Sub
Public Sub MergeRowsWithSameContent(gvw As GridView)
For rowIndex As Integer = gvw.Rows.Count - 2 To 0 Step -1
Dim row As GridViewRow = gvw.Rows(rowIndex)
Dim previousRow As GridViewRow = gvw.Rows(rowIndex + 1)
For i As Integer = 0 To row.Cells.Count - 1
If row.Cells(i).Text = previousRow.Cells(i).Text Then
row.Cells(i).RowSpan = If(previousRow.Cells(i).RowSpan < 2, 2, previousRow.Cells(i).RowSpan + 1)
previousRow.Cells(i).Visible = False
End If
Next
Next
End Sub
P.S: Shameless port of this wonderful code I have been using for years.