Compare two datatables to see which fields have been changed - vb.net

I am trying to compare two datatables, each datatable will hold a snapshot of a parts list at a point in time. So for example DT1 will show the parts as they were on the 31/3 and DT2 will show as they are on the 15/04. This will eventually need to highlight a datagridview (green is new record, red is deleted record, yellow is changed cell)
I can do (and have) this with nested for next loops but it is slow to process 1000 records.
I am presently adding a dummy field to each row in the parts list "NAAANNAA" where 'N' is field not changed and 'A' is field amended between the two DTs. 'I' for inserted and 'D' for deleted.
The paint method on the cell just has style.backcolor=color.yellow where string.instr(dgv.currentcell.columnindex)="A" (air code, not need pc at the moment)
Is there an easier way to do with Linq?
App is VB in VS2010 but soon to be either VB or C# in VS2019. Winforms with a SQL backend.
From memory elsewhere in the code it does the nested for next loops, this is just the cellformatting
--> Updated with some code
Private Sub dgvDOIssue_CellFormatting(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles dgvDOIssue.CellFormatting
Dim changed_flags As String = ""
Try
If UcDOCompare1.DataGridView1.Rows.Count >= e.RowIndex Then
With UcDOCompare1.DataGridView1
changed_flags = .Rows(e.RowIndex).Cells(.Columns.Count - 1).Value
If Strings.Mid(changed_flags, e.ColumnIndex + 1, 1) = "I" Then
e.CellStyle.BackColor = Color.Green
ElseIf Strings.Mid(changed_flags, e.ColumnIndex + 1, 1) = "A" Then
e.CellStyle.BackColor = Color.Yellow
ElseIf Strings.Mid(changed_flags, e.ColumnIndex + 1, 1) = "G" Then
e.CellStyle.BackColor = Color.Pink
Else
e.CellStyle.BackColor = Color.White
End If
End With
End If
Catch ex As Exception
End Try
End sub

Related

RowIndex in CellValueChanged event of my DataGridView is -1

I need to change some data before showing into a DataGridView and I'm using CellValueChanged event. The problem is that e.RowIndex is -1 when datagridview is filling with a datasource.
Private Sub dgvList_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles dgvList.CellValueChanged
' Convert date to Persian if the edited cell has date
If dgvList.Rows(e.RowIndex).Cells.Item(e.ColumnIndex).ValueType.ToString = "" Then
' Do something
End If
End Sub
I update values using it's data source. I change DataTable and the DataGridView updates automatically:
Dim MyRow = dstList.Tables("list").NewRow
MyRow.Item("date_reported") = PersianDateTime.Parse(txtDateReport.Text).ToDateTime
MyRow.Item("description") = txtDescription.Text
MyRow.Item("duration") = txtDuration.Text
MyRow.Item("coworkers") = txtCoworkers.Text
MyRow.Item("progress") = numProgress.Value
MyRow.Item("problems") = txtProblems.Text
MyRow.Item("date_created") = Now.Date
' Add this row to the dataset
dstList.Tables("list").Rows.Add(MyRow)
' Apply changes to database
UpdateList()
That's normal. I think it's when the column headers are set. You just have to add an If statement to the event handler to filter that out.
If e.RowIndex <> -1 Then
'It's a valid cell.
End If
If e.RowIndex > -1 AndAlso e.ColumnIndex > -1 Then
Debug.Print(dgvList(e.ColumnIndex, e.RowIndex).ValueType.ToString)
Debug.Print(dgvList(e.ColumnIndex, e.RowIndex).Value.ToString)
End If
Works fine for me.
How are you changing values?
Also post part of the code that changes it. As source of change gives the index not the result (the code you posted).

Formatting rows in a datagridview

I have a few datagridview's, listing different bits and bobs from a mysql database.
One of them has a column called 'outcome'.
This can be either 'Yes', 'Pending' or 'No'.
I need to format this list based on that value.
I'm using the following at the moment...
Private Sub nb_myleads_dgv_CellFormattin(ByVal sender As Object, ByVal e As DataGridViewCellFormattingEventArgs) Handles nb_myleads_dgv.CellFormatting
If e.ColumnIndex = nb_myleads_dgv.Columns("outcome").Index Then
If e.Value.ToString = "Yes" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkGreen
ElseIf e.Value.ToString = "Pending" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkOrange
ElseIf e.Value.ToString = "No" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkRed
End If
End sub
End If
It seem that I have to have my column 'outcome' visible in the DGV for this to work.
I have gotten around this by setting this column width to 3 pixels, but that seems a little dirty. Is it not possible to format cells in a datagridview based on the value of a hidden column?
Thanks in advance
Why not just loop through the rows with a for each and colorize based on the cell value. It won't matter if it's visible or not.
For Each row As DataGridViewRow In DataGridView1.Rows
If Not row.IsNewRow Then
Select Case row.Cells(2).Value.ToString
Case "Yes"
row.DefaultCellStyle.BackColor = Color.DarkGreen
Case "Pending"
row.DefaultCellStyle.BackColor = Color.DarkOrange
Case "No"
row.DefaultCellStyle.BackColor = Color.DarkRed
End Select
End If
Next
Where in this case, column 3 (cells(2) is hidden. You would do this after populating the grid instead of in the cellformatting
I believe your problem is that cell has to be visible, otherwise it will never pass if statement.
The CellFormatting event occurs every time each cell is painted, so
you should avoid lengthy processing when handling this event. This
event also occurs when the cell FormattedValue is retrieved or its
GetFormattedValue method is called.
Link
I would get rid off this if statement and do something like this:
Dim str as string = dataGridView1.Rows[e.RowIndex].Cells["outcome"].Value.ToString
If str = "Yes" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkGreen
ElseIf str = "Pending" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkOrange
ElseIf str = "No" Then
nb_myleads_dgv.Rows(e.RowIndex).DefaultCellStyle.BackColor = Color.DarkRed
End If

VB.net DataGridview: Represent Boolean column using images

I have a DataGridView dgv showing the content of an underlying DataView dv, which I use for filtering the row entries. (dgv.DataSource = dv)
Two of the columns in the DataView are Boolean types and show up as the default checkbox formatting of VB, however, I would like them to show as squares (or rectangles) in colors red and green for False and True respectively.
I know that for a DataGridView with a column of the type DataGridViewImageColumn I could simply generate an image and show it with something similar to this:
bmp = New Bitmap(20, 10)
Using g As Graphics = Graphics.FromImage(bmp)
If VarIsValid Then
g.FillRectangle(Brushes.GreenYellow, 0, 0, bmp.Width - 1, bmp.Height - 1)
Else
g.FillRectangle(Brushes.Red, 0, 0, bmp.Width - 1, bmp.Height - 1)
End If
g.DrawRectangle(Pens.Black, 0, 0, bmp.Width - 1, bmp.Height - 1)
End Using
row.Cells("VarIsValid").Value = bmp
But I have no Idea how to do something similar with a column originating from a linked DataView; much less when the column is not even an image column.
I considered changing the underlying DataView to contain an image, but then I don't know how I would filter by the value of that column. Thus I hope there is some way to simply change the visualization without changing the underlying structure.
Use Cell Formatting event handler
Private Sub dgv_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles dgv.CellFormatting
If e.RowIndex < 0 OrElse e.ColumnIndex < 0 Then Exit Sub
'You can check that column is right by ColumnIndex
'I prefer using a name of the DataGridViewColumn,
'because indexes can be changed while developing
If Me.dgv.Columns(e.ColumnIndex).Name.Equals("PredefinedColumnName") = True Then
If CBool(e.Value) = True Then
e.Value = My.Resources.GreenSquare 'image saved in the resources
Else
e.Value = My.Resources.RedSquare
End If
End If
End Sub
For a simple color fill operation, there is no need to use a bitmap. Just subscribe to the CellPainting event on the DatagridView.
Private Sub dgv1_CellPainting(sender As Object, e As DataGridViewCellPaintingEventArgs) Handles dgv1.CellPainting
Const checkboxColumnIndex As Int32 = 0 ' set this to the needed column index
If e.ColumnIndex = checkboxColumnIndex AndAlso e.RowIndex >= 0 Then
Dim br As Brush
If CBool(e.Value) Then
br = Brushes.GreenYellow
Else
br = Brushes.Red
End If
e.Graphics.FillRectangle(br, e.CellBounds)
e.Paint(e.ClipBounds, DataGridViewPaintParts.Border)
e.Handled = True
End If
End Sub
For more information, see: Customizing the Windows Forms DataGridView Control

Retrieve Row Index From DataView using LINQ

My DataView's Table attribute is assigned to one of my DataTables. The DataView is then set as my DataGridView's DataSource. Like so:
View.Table = DataSet1.Tables("dtArticles")
dgvArticles.DataSource = View
My goal is to perform some formatting on some given rows. Unfortunately, the only way I've found to do so is using the CellFormatting event. This is quite long since it must go through each row and verify if the row needs some formatting (bold font, backcolor).
CellFormatting
Private Sub dgvArticles_CellFormatting(sender As Object, e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles dgvArticles.CellFormatting
Dim drv As DataRowView
If e.RowIndex >= 0 Then
If e.RowIndex <= DataSet1.Tables("dtArticles").Rows.Count - 1 Then
drv = View.Item(e.RowIndex)
Dim c As Color
'Bolds if it is standard, makes it regular if it's not standard and already bold
If drv.Item("Standard").ToString = "Yes" Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = New Font("Microsoft Sans Sherif", 8, FontStyle.Bold)
End If
'Test if Standard is "No" and if the row is currently bold, if it is, put it back to regular
If drv.Item("Standard").ToString = "No" And Not dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font Is Nothing Then
If dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font.Bold Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = New Font("Microsoft Sans Sherif", 8, FontStyle.Regular)
End If
End If
'Puts a red color to the rows who are not available
If drv.Item("Available").ToString = "No" Then
'Change back color
c = Color.LightSalmon
e.CellStyle.BackColor = c
End If
End If
End If
End Sub
What this does is for each row's cells added to the DataGridView; it checks if my Standard column's current value is equal to Yes or No. Depending on the result, it will bold / un bold the row. The same principal goes for my Available column. If the Available value is equal to "No" then I put a light red back color to the row. This event is raised many times (10,000 articles with a DataTable of 10 or so columns).
About 2.2-2.7 seconds on average
Iterating through the DataView
Dim i As Integer = 0
For Each dt As BillMat.dtArticlesRow In View.Table.Rows
If dt.Standard = "Oui" Then dgvArticles.Rows(i).DefaultCellStyle.Font = New Font("Microsoft Sans Sherif", 8, FontStyle.Bold)
If dt.Available = "Non" Then dgvArticles.Rows(i).DefaultCellStyle.BackColor = Color.LightSalmon
i += 1
Next
About 1.5-1.7 seconds on average
Is it possible to use LINQ to select the RowIndex of which Standard column is equal to "Yes" and which Available column is equal to "No" ? If it is, wouldn't using the index to apply the formatting be much quicker than iterating through the whole 10,000 articles?
I don't know if using Link is the best solution for your problem.
You should first try to simplify the code in DataGridView.CellFormatting event. Some possibles ways:
Create one private instance of regular and bold Font to avoid to create multiples instance in the method
I guess you don't need to test the second condition if first is passed. So If End If + If End If could be replaced to If Else End If (don't need to test the two expressions)
Use If x AndAlso y Then instead of If x And y Then : if first expression is false, the second won't be tested
I don't understand the purpose of this If e.RowIndex <= DataSet1.Tables("dtArticles").Rows.Count - 1 Then. If the goal is not test if the row is the NewRow (with AllowUserToAddRows property set to True), simplier would be this
If DataGridView1.Rows(e.RowIndex).IsNewRow then
Private _RegularFont As New Font("Microsoft Sans Sherif", 8, FontStyle.Regular)
Private _BoldFont As New Font("Microsoft Sans Sherif", 8, FontStyle.Bold)
Private Sub dgvArticles_CellFormatting(sender As Object, e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles dgvArticles.CellFormatting
Dim drv As DataRowView
If e.RowIndex >= 0 Then
If Not dgvArticles.Rows(e.RowIndex).IsNewRow Then
drv = View.Item(e.RowIndex)
'Bolds if it is standard, makes it regular if it's not standard and already bold
If drv.Item("Standard").ToString = "Yes" Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = _BoldFont
Else 'First condition tested, test second
'Test if Standard is "No" and if the row is currently bold, if it is, put it back to regular
If drv.Item("Standard").ToString = "No" AndAlso Not dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font Is Nothing Then
If dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font.Bold Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = _RegularFont
End If
End If
End If
'Puts a red color to the rows who are not available
If drv.Item("Available").ToString = "No" Then
'Change back color
Dim c As Color
c = Color.LightSalmon
e.CellStyle.BackColor = c
End If
End If
End If
End Sub
Using link, you could do something like this (for your purpose, you should not iterate into DataView.Rows to retrieve index, but directly in DataGridView.Rows):
Dim _BoldFont As New Font("Microsoft Sans Sherif", 8, FontStyle.Regular)
Dim q = From r In Me.DataGridView1.Rows
Where CType(r, DataGridViewRow).Cells("Standard").Value.ToString = "Oui"
Select r
For Each item In q
CType(item, DataGridViewRow).DefaultCellStyle.Font = _BoldFont
Next
Dim q = From r In Me.DataGridView1.Rows
Where CType(r, DataGridViewRow).Cells("Available").Value.ToString = "Non"
Select r
For Each item In q
CType(item, DataGridViewRow).DefaultCellStyle.BackColor = Color.LightSalmon
Next

vb.net, how to set different text colors in data table

I am using vb.net in my project. I have a datagridview bounded to a datatable m_table which has a column called Price_Change with decimal values. I want to display text in datagridview in green if price change>0 and red otherwise. I cannot simply use the following formatting because the bounded data table m_table is constructed in my code, not directly through data base.
DataGridView.Rows(0)Cells(0).Style.ForeColor=COLOR.BLACK
Code looks like
Dim rowText As DataRow = m_table.NewRow
rowText("Price Change")=10.00 'assign values to price change column
' there is no color formating for data table
I wonder if cellformatting event can be used for this purpose. Would it slow down the load of datagridview? Here is a [link]
http://msdn.microsoft.com/en-us/library/system.windows.forms.datagridview.cellformatting.aspx
Private Sub DGVTable_CellFormatting(sender As Object, e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles DGVTable.CellFormatting
If Me.DGVTable.Columns(e.ColumnIndex).Name = columnPriceChange Then
If e.Value IsNot Nothing Then
Dim change As Decimal = CType(e.Value, Decimal)
If change >= 0 Then
e.CellStyle.ForeColor = Color.Green
Else
e.CellStyle.ForeColor = Color.Red
End If
End If
End If
End Sub