Find Whether Specific Row Contains a Selected (Highlighted) Cell - vb.net

At the moment, I've created a loop which runs through every cells in my datagridview to find if any cells are selected (highlighted), and if so that cell's row/ column header backcolor changes. The problem I have with this is that once the row and column count start to become a large amount, I start experiencing lag. So I wondering does anybody know a way of instantly being able to find whether a row or column contains a selected or highlighted cell without using a loop.
I was hoping that something like me.datagridview1.rows(1).selectedcells.count would exist, but haven't been able to find something like that, that works.
The other option (which I'd prefer not to do), is color the headercells as the selection is made.
Here is what I am using to find the selections. I have it looping through only the displayed cells to reduce the lag (which works), but I need the headercells colored, even when the selected cells are out of view (which then doesn't work).
a = .FirstDisplayedCell.RowIndex
Do While a < .FirstDisplayedCell.RowIndex + .DisplayedRowCount(True)
b = .FirstDisplayedCell.ColumnIndex
Do While b < .FirstDisplayedCell.ColumnIndex + .DisplayedColumnCount(True)
If .Rows(a).Cells(b).Selected = False Then
.Rows(a).HeaderCell.Style.BackColor = colorfieldheader
.Columns(b).HeaderCell.Style.BackColor = colorfieldheader
End If
b += 1
Loop
a += 1
Loop
a = .FirstDisplayedCell.RowIndex
Do While a < .FirstDisplayedCell.RowIndex + .DisplayedRowCount(True)
b = .FirstDisplayedCell.ColumnIndex
Do While b < .FirstDisplayedCell.ColumnIndex + .DisplayedColumnCount(True)
If .Rows(a).Cells(b).Selected = True Then
.Rows(a).HeaderCell.Style.BackColor = colorfieldheaderhighlight
.Columns(b).HeaderCell.Style.BackColor = colorfieldheaderhighlight
End If
b += 1
Loop
a += 1
Loop

You can do something like this C# code:
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
foreach (DataGridViewCell cell in dataGridView1.SelectedCells)
{
dataGridView1.Rows[cell.RowIndex].DefaultCellStyle.BackColor = Color.Red;
}
}
Ensure that the event handler is attached after data binding.
If you are highlighting the row same a selected cell (blue), you might want to set SelectionMode as FullRowSelect for the DataGridView. Like this:
dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;

place this code in your data grid view MouseDown event ...
private void dataGridView1_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Left )
{
this.dataGridView1.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Red;
}
}
and for more info use how to highlight specific row in datagridview in c#

Related

Access VBA Public Function - Variable for Control and Field?

I want to set up a public function like the following:
(A)
Public Function CheckYNField(chkControl As Control, chkField As String)
If chkField = "Y" Then
chkControl = -1
ElseIf chkField = "N" Then
chkControl = 0
Else: chkControl = 0
End If
End Function
(B)
Public Function CheckYNFlipper(chkControl As Control, chkField As String)
If chkField = "Y" Then
chkField = "N"
chkControl = 0
ElseIf chkField = "N" Then
chkField = "Y"
chkControl = -1
Else: chkField = "Y"
chkControl = -1
End If
End Function
The reason for this is, that I have a form, which has an underlying SQL table. I have no control over the data, but I must represent it, for the ability to maintain it. I have 10 fields, in the underlying table, which have a Y or N as their values. The data types are actually nvarchar(10). At the same time, I have to show these fields as checkbox controls, on the form, for ease of use.
The above code, is an attempt I am making to A - set the checkbox control to align with the current value in the table--> -1 = Y and 0 = N, and to B - update the table value, and switch the check box to checked or unchecked, from what it was, to the opposite, based on the onclick event of that control.
I want to make chkField and chkControl variables to the public function, that would be the table-field, and the form-control. I can't seem to get the right syntax, and was hoping someone might have clarification on how to do this.
for the form load and current, I tried this:
CheckYNField Forms("frmFormNameA").Controls(chkCheckNameA), tblTableName.FieldA
for the on click, I tried this:
CheckYNFlipper Forms("frmFormNameA").Controls(chkCheckNameA), tblTableName.FieldA
I've tried some other methods, but doesn't seem to be working. I'm doing something wrong, but I can't tell what. Appreciate any tips!
Edit/Update:
I tried Kostas K.'s solution, abandoning the idea of making a public functions with parameters for the fields and controls. I put the following, on load and on current:
With Me
If .txtfieldboundtablefieldA.Value = "Y" Then
.unboundchkA.Value = True
ElseIf .txtfieldboundtablefieldA.Value = "N" Then
.unboundchkA.Value = False
Else: .unboundchkA.Value = False
End If
End With
This is on a continuous form, so that it can show like a giant grid. There are the identifying bound field controls, and then a series of these checkboxes, to display the Y/N true/false status of each of these particular fields. I can't bound the checkboxes to the fields, because it changes the field value in the table to -1 or 0, and we need it to stay Y or N. I added a bound text field, to hold the table/field value for each row (hence the call to a text box control in the above revised code). The checkbox is unbound, and is there to display the field value, and allow the user to check and uncheck, so I can use on-click code to change the table field value between Y and N.
The above code is not seeming to show the correct checkbox value for each bound text field, based on each row. It shows based on the row that currently has focus. If I click on a row, where the table field is Y, all rows checkboxes on the form show true. If I move to a row, where the table field is N, all checkboxes for all rows change to false. I am struggling to just initially get 1 checkbox to show accurately, on every row of the continuous form, based on every record in the table. This is a small table, like 30 records. I really didn't think it would be so difficult to present this the way we need to.
Any ideas, how I could better do this?
Edit:
Set the Control Source of the checkbox to:
= IIf([YourYesNoField] = "Y", -1, 0)
In order to update when clicked:
Private Sub chkCheckNameA_Click()
Dim yesNo As String
yesNo = IIf(Me.chkCheckNameA.Value = 0, "Y", "N") 'reverse value
CurrentDb.Execute "Update [YourTable] SET [YourYesNoField]='" & yesNo & "' WHERE [ID]=" & Me![ID], dbFailOnError
Me.Requery
End Sub
You could try something this.
Check the Y/N field and assign the function's boolean return value to the checkbox (A).
On the checkbox click event, check its value and update the Y/N field (B).
'Form Load
Private Sub Form_Load()
With Me
.chkCheckNameA.Value = CheckYNField(![FieldA])
End With
End Sub
'Click
Private Sub chkCheckNameA_Click()
With Me
![FieldA] = IIf(.chkCheckNameA.Value = 0, "N", "Y")
End With
End Sub
'True returns -1, False returns 0
Private Function CheckYNField(ByVal chkField As String) As Boolean
CheckYNField = (chkField = "Y")
End Function
chkControl is a Control, so you need to access a property of that control. Try changing your code to:
Public Function CheckYNField(chkControl As Control, chkField As String)
If chkField = "Y" Then
chkControl.Value = -1
ElseIf chkField = "N" Then
chkControl.Value = 0
Else: chkControl.Value = 0
End If
End Function
and then the same idea in your other function.

Copy and Paste Selected Rows in an unbound DataGridView

I've tried changing the ClipboardCopyMode to "EnableWithoutHeaderText" in the DataGridView properties but this did not work. Also, I tried doing this programmatically with the code below, but it did not work either. Please help.
Private Sub DataGridView1_CellContentClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
Me.DataGridView1.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText
End Sub
You can clone selected rows with values of cells, then you can use InsertRange to insert copies cells. Pay attention this way will work for an unbound DataGridView and if your DataGridView is bound, then you should copy records of the DataSource of control.
C#
var insertAt = 0;
var rows = dataGridView1.SelectedRows.Cast<DataGridViewRow>()
.OrderBy(r=>r.Index)
.Select(r=>{
var clone = r.Clone() as DataGridViewRow;
for (int i = 0; i < r.Cells.Count; i++)
clone.Cells[i].Value= r.Cells[i].Value;
return clone;
}).ToArray();
dataGridView1.Rows.InsertRange(insertAt, rows);
VB
Dim insertAt = 0
Dim rows = DataGridView1.SelectedRows.Cast(Of DataGridViewRow) _
.OrderBy(Function(r) r.Index) _
.Select(Function(r)
Dim clone = DirectCast(r.Clone(), DataGridViewRow)
For i = 0 To r.Cells.Count - 1
clone.Cells(i).Value = r.Cells(i).Value
Next
Return clone
End Function) _
.ToArray()
DataGridView1.Rows.InsertRange(insertAt, rows)
Note
DataGridView.Rows collection also have InsertCopies method. But the method can only copy a contiguous range of rows. While above code can copy a non-contiguous selection as well.
I used OrderBy(r=>r.Index) to insert rows in the same order which you see in grid not with order of selecting them.
DataGridViewRow.Clone method clones a row with all its properties but not with cell values, so I copied values using the for loop.
You can simply create an extension method based on above code. It would be more reusable.

change color of every datagridview row containing a certain value

Is this possible? what i'm currently doing is just looping through each row to change the color, but this is very slow since i'm also running a query and based on the results I change the color. Here's an example
Part No | Other Columns
-----------------------
Part A | 123456
Part B | 123456
Part C | 123456
Part A | 123456
Part A | 123456
My query's where is Where Part_No = 'Part A', with my current method I would just be running the same query 3 times. What I want to do is get the distinct Values on the first column, check those in the query and change the color of the rows containing that value.
answered here: How to change row color in datagridview?
===
I was just investigating this issue (so I know this question was published almost 3 years ago, but maybe it will help someone... ) but it seems that a better option is to place the code inside the RowPrePaint event so that you don't have to traverse every row, only those that get painted (so it will perform much better on large amount of data:
Attach to the event
this.dataGridView1.RowPrePaint
+= new System.Windows.Forms.DataGridViewRowPrePaintEventHandler(
this.dataGridView1_RowPrePaint);
The event code
private void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
{
if (Convert.ToInt32(dataGridView1.Rows[e.RowIndex].Cells[7].Text) < Convert.ToInt32(dataGridView1.Rows[e.RowIndex]..Cells[10].Text))
{
dataGridView1.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Beige;
}
}
An economical way is to use the CellFormatting event. This will fire when that cell is displayed the first time. It will only fire for the visible rows so it should be faster.
Then I loop through the grid view to do something like update the data of all colored rows
If the data comes from db (the OP mentions a query) you ought to be acting on the data like the datasource. The DGV is just the user's view of the data. Rather than acting on rows of a certain color, act on the part number or whatever determines the color. You could also create a view of the records with this or that part number and act/loop on that to simplify the process.
Since the data can change, you'll want to handle 2 events: CellFormatting and CellValueChanged
Private Sub dgv1_CellFormatting(...) Handles dgv1.CellFormatting
' default start up color
If e.ColumnIndex = 3 Then
e.FormattingApplied = ColorMyRow(e.RowIndex, e.ColumnIndex)
Else
e.FormattingApplied = False
End If
End Sub
Private Sub dgv1_CellValueChanged(...) Handles dgv1.CellValueChanged
' if the target cell changes, update
If e.ColumnIndex = 3 Then
ColorMyRow(e.ColumnIndex, e.RowIndex)
End If
End Sub
' DRY
Private Function ColorMyRow(rowIndex As Int32, colIndex As Int32) As Boolean
Dim bass As Color = Color.PeachPuff
Dim pike As Color = Color.SeaShell
Dim salmon As Color = Color.Salmon
Select Case dgv1.Rows(rowIndex).Cells(colIndex).Value.ToString
Case "Bass"
dgv1.Rows(rowIndex).DefaultCellStyle.BackColor = bass
Return True
Case "Pike"
dgv1.Rows(rowIndex).DefaultCellStyle.BackColor = pike
Return True
Case "Salmon"
dgv1.Rows(rowIndex).DefaultCellStyle.BackColor = salmon
Return True
End Select
Return False
End Function
The color will be updated whether a) the user edits the cell, b) you change a cell value ( dgv1.Rows(0).Cells(3).Value = "Mermaid" ) or c) you change the datasource ( dtParts.Rows(0)(3) = "Pike").
Finally, rather then trying to loop on the blue rows, you can query the DataSource, in this case, a DataTable:
Dim bassRows = dtSample.Select("Fish = 'Bass'")
For Each dr As DataRow In bassRows
dr("Fish") = "Pike"
Next
The color will automagically change.

First 30 % displayed columns in DataGridView

First of all: 30% doesn't matter. That's a question of design. We can also say the first 3 Displayed Columns.
In my DataGridView I am using BackgroundColors for Rows to pass the User some information.
To keep this information visible to the user while Rows are being selected the first 30% of the columns should get the same SelectionBack/ForeColor as the Back/ForeColor.
So far that has never been a problem using
.cells(0).Style.SelectionBackColor = .cells(0).Style.Backcolor
(and so on).
Now I added the function that allows the user to reorder the Columns which makes the following Statement become true:
ColumnIndex != DisplayedIndex.
That statement beeing true makes the SelectionBackColor-Changed cells be somewhere mixed in the row and not in the first columns anymore. It still does the job, but looks terrible.
Is there something like a "DisplayedColumns" collection in order of the .DisplayedIndex Value that i could use to call the first few DisplayedColumns? If not, how could I effectivly create one my own?
Edit:
The user can also hide specific columns, that do not matter for him. So we have to be aware of Column.DisplayedIndex and Column.Visble
Got it working with the following code:
Try
' calculate what is thirty percent
Dim colcount As Integer = oDic_TabToGridview(TabPage).DisplayedColumnCount(False)
Dim thirtyPercent As Integer = ((colcount / 100) * 30)
' Recolor the first 30 % of the Columns
Dim i As Integer = 0
Dim lastCol As DataGridViewColumn = oDic_TabToGridview(TabPage).Columns.GetFirstColumn(DataGridViewElementStates.Visible)
While i < thirtyPercent
.Cells(lastCol.Index).Style.SelectionBackColor = oCol(row.Item("Color_ID") - 1)
.Cells(lastCol.Index).Style.SelectionForeColor = Color.Black
lastCol = oDic_TabToGridview(TabPage).Columns.GetNextColumn(lastCol, DataGridViewElementStates.Visible, DataGridViewElementStates.None)
i += 1
End While
Catch ex As Exception
MsgBox(ex.Message & vbNewLine & ex.StackTrace)
End Try
Let us first assume you are coloring your rows somehow resembling the following manner:
Me.dataGridView1.Rows(0).DefaultCellStyle.BackColor = Color.PowderBlue
Me.dataGridView1.Rows(1).DefaultCellStyle.BackColor = Color.Pink
' ...etc.
In the DataGridView.CellPainting event handler, you can determine if the painting cell falls within the first N columns by utilizing the DataGridViewColumnCollection.GetFirstColumn and DataGridViewColumnCollection.GetNextColumn methods.
If the cell belongs to one these columns, set the cell's SelectionBackColor to the cell's BackColor. Otherwise set it to the default highlighting color.
Dim column As DataGridViewColumn = Me.dataGridView1.Columns.GetFirstColumn(DataGridViewElementStates.Visible)
e.CellStyle.SelectionBackColor = Color.FromName("Highlight")
' Example: Loop for the first N displayed columns, where here N = 2.
While column.DisplayIndex < 2
If column.Index = e.ColumnIndex Then
e.CellStyle.SelectionBackColor = e.CellStyle.BackColor
Exit While
End If
column = Me.dataGridView1.Columns.GetNextColumn(column, DataGridViewElementStates.Visible, DataGridViewElementStates.None)
End While
As a side note: You may want to consider changing the ForeColor on these cells for readability - depending on your row's BackColor choices. Likewise, if only a single cell is selected from one these first N columns, it can be difficult to notice.

Winforms - Datagridview dynamically change linkbutton text

I am using Winforms datagridview (n-tier architecture) to populate from a dataset. I want to have a linkbutton as the last column which should change its text based on the value of two columns. While I have managed to get the linkbutton, I cannot get the value to change. I need the value to change so that when the linkbutton is clicked, it should open up different windows. My code is as below
Private Sub ShowProductRequisitionsInListView(ByVal data As DataSet, ByVal dgv As DataGridView)
dgvRequisitionDetails.Columns.Clear()
dgvRequisitionDetails.DataSource = Nothing
dgvRequisitionDetails.DataSource = data.Tables(0)
dgvRequisitionDetails.Columns(0).Width = 80
dgvRequisitionDetails.Columns(0).HeaderText = "Product Code"
dgvRequisitionDetails.Columns(1).Width = 180
dgvRequisitionDetails.Columns(1).HeaderText = "Product Name"
dgvRequisitionDetails.Columns(2).Width = 150
dgvRequisitionDetails.Columns(2).HeaderText = "Sales UOM"
dgvRequisitionDetails.Columns(3).Width = 60
dgvRequisitionDetails.Columns(3).HeaderText = "Qty. Reqd."
dgvRequisitionDetails.Columns(3).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvRequisitionDetails.Columns(3).DefaultCellStyle.Format = "N3"
dgvRequisitionDetails.Columns(4).Width = 105
dgvRequisitionDetails.Columns(4).HeaderText = "Qty. In Stock"
dgvRequisitionDetails.Columns(4).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
dgvRequisitionDetails.Columns(4).DefaultCellStyle.Format = "N3"
Dim lnk As DataGridViewLinkColumn = New DataGridViewLinkColumn
dgvRequisitionDetails.Columns.Add(lnk)
lnk.HeaderText = "Action"
lnk.Text = "Click Here"
lnk.Name = "lnkAction"
lnk.UseColumnTextForLinkValue = True
End Sub
If the difference between the QtyReqd and QtyInStock is a negative value, the link button should read as "Not Available" and if there is enough stock it should read as "Available". Based on these two values different windows will open upon clicking the link
I tried to check the condition in the DataBindingComplete event, but its not working. What am I doing wrong here?
CL
I think that the easiest way is to add a computed column to the data table and use this as the data bound item for the link column. Something like this:
Dim expr As String = "IFF((([QtyInStock] - [QtyReqd]) >= 0), 'Available', 'Not Available')"
data.Tables(0).Columns.Add("Action", GetType(String), expr)
In the datagrid designer add a handler for: OnRowDataBound="someEvent_RowDataBound"
In the code behind you will have a function:
protected void someEvent_RowDataBound(object sender, System.Web.UI.WebControls.GridViewRowEventArgs e)
{
If(e.Row.RowType == DataControlRowType.DataRow){
((linkbutton)e.Row.Cells[someindex].controls(0)).Text = "somevalue";
}
}