How can I accuretely determine number of rows in DataGridView except of new row (the row with asterisk)?
If I start typing into new row and then cancel it by pressing Esc, another new row created in the meantime disappears and original row becomes new row again. But it no longer has its IsNewRow
property set to true
. And DataGridView.NewRowIndex
is suddenly set to -1
.
How I can correctly count rows except the new row including the above special case? (Temporary reset of NewRowIndex
to -1
(also affecting IsNewRow
) is a "feature" of DataGridView.)
(C# or VB – whatever you prefer.)
For those who were unable to find the special case above –
just handle DataGridView.UserDeletedRow
and there it is:
Sub DataGridView1_UserDeletedRow1(sender As Object, e As EventArgs) _
Handles DataGridView1.UserDeletedRow
' reveal problem with DataGridView1.NewRowIndex
Debug.Print($"NewRowIndex={DataGridView1.NewRowIndex}")
' reveal problem with row.IsNewRow
For Each row As DataGridViewRow In DataGridView1.Rows
Debug.Print($"Row {row.Index}: IsNewRow={row.IsNewRow}")
Next
End Sub
Steps:
DataGridView1
.DataGridView1
.a
.NewRowIndex=-1
, but it should print number >= 0
. Last IsNewRow
value is false
, but it should be true
.There is no other event (than already used DataGridView.UserDeletedRow
) suitable for calculation covering the special case. And in that event, key values are temporarily incorrect – see the source.
The only way seems to be take the calculations from the .NET reference source of DataGridView
and DataGridViewRow
.
The calculations below assume that
DataGridView.AllowUserToAddRows
is true
.Let's calculate:
= DataGridView1.Rows.Count - (DataGridView1.AllowUserToAddRows ? 1 : 0)
And unlike original properties which have problems, these calculations seem to always succeed:
DataGridView.NewRowIndex
= (DataGridView1.AllowUserToAddRows ? DataGridView1.Rows.Count - 1 : -1)
DataGridViewRow.IsNewRow
= row.DataGridView.AllowUserToAddRows && (row.Index == row.DataGridView.Rows.Count - 1)
Implemented as vb.net extension methods:
''' <summary>
''' Row count including only standard rows, i.e. without new row.
''' </summary>
<Extension>
<DebuggerStepThrough>
Function StandardRowCountᛎ(dataGridView As DataGridView) As Integer
Return dataGridView.Rows.Count - If(dataGridView.AllowUserToAddRows, 1, 0)
End Function
''' <summary>
''' The same as <see cref="DataGridView.NewRowindex"/> but reliable also after cancelled new record.
''' </summary>
<Extension>
<DebuggerStepThrough>
Function NewRowIndexᛎ(dataGridView As DataGridView) As Integer
Return If(dataGridView.AllowUserToAddRows, dataGridView.Rows.Count - 1, -1)
End Function
''' <summary>
''' The same as <see cref="DataGridViewRow.IsNewRow"/> but reliable also after cancelled new record.
''' </summary>
<Extension>
<DebuggerStepThrough>
Function IsNewRowᛎ(row As DataGridViewRow) As Boolean
Return row.DataGridView.AllowUserToAddRows AndAlso row.Index = row.DataGridView.Rows.Count - 1
End Function
After replacing standard calls by calls to these methods, I noticed that couple of other subtle bugs got fixed in my project, which seemed to be related to presence of new record row.
Check this post on MSDN: system.windows.forms.datagridview.allowusertoaddrows (carefully read the Remarks section) and this MSDN Forum post datagridview-cancel-add-new-row
You may use DataGridView.AllowNew
property as workaround for your solution.
Set the DataGridView.AllowNew = False
This should remove the new insert row
Count DataGridView rowsDataGridView.Rows.Count
or DataGridView.RowCount
(whichever is your requirement)
Again set DataGridView.AllowNew = True
to show blank row for adding new record (if required)
I have NOT tested the code snippet but this should solve the problem
Edit:
Just found two more articles related to your question, may be useful for your reading:
adding-a-new-row-to-the-datagridview-programmatically-datagridviewallowusertoaddrow-false
how-to-cancel-an-add-new-by-pressing-escape
You can use the something like this
DataGridView dg = ...;
var rowCount = dg.Rows.GetRowCount(DataGridViewElementStates.Visible) -
(dg.NewRowIndex >= 0 ? 1 : 0);
I've tested it in both bound and unbound mode and it works correctly. Btw, I wasn't able to reproduce the special case you mentioned - IsNewRow
property was always returning a correct value.
Here is my test code:
//#define fBoundMode
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace Samples
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
#if fBoundMode
dg.DataSource = new BindingList<Data>();
#else
dg.Columns.Add("Foo", "Foo");
dg.Columns.Add("Bar", "Bar");
#endif
var status = new Label { Dock = DockStyle.Bottom, AutoSize = false, Parent = form };
var timer = new Timer { Interval = 250, Enabled = true };
timer.Tick += (sender, e) =>
{
var rowCount = dg.Rows.GetRowCount(DataGridViewElementStates.Visible) - (dg.NewRowIndex >= 0 ? 1 : 0);
status.Text = "Rows: " + rowCount;
};
Application.Run(form);
}
#if fBoundMode
class Data
{
public string Foo { get; set; }
public string Bar { get; set; }
}
#endif
}
}
UPDATE: As for the special case (which you should have clearly provided from the beginning - event now it's not clear until one hits the code example, and I don't think this site is a place for puzzles) I find it very exceptional and the "correct" solution provided in your own answer
rowCount = dg.Rows.Count - (dg.AllowUserToAddRows? 1 : 0);
may work for your specific needs, but cannot be used as a general advice (which it seems to pretend to be) because apparently doesn't work with invisible rows or bound mode to a data source with AddNew disabled, as you may see in the example below:
//#define fBoundMode
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
namespace Samples
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form();
var dg = new DataGridView { Dock = DockStyle.Fill, Parent = form };
#if fBoundMode
dg.DataSource = new BindingList<Data>(new List<Data> { new Data { Foo = "a" }, new Data { Foo = "b" } }) { AllowNew = false };
#else
dg.Columns.Add("Foo", "Foo");
dg.Columns.Add("Bar", "Bar");
dg.Rows.Add("a", "a");
dg.Rows.Add("b", "b");
dg.Rows.Add("c", "c");
dg.Rows[1].Visible = false;
#endif
var status = new Label { Dock = DockStyle.Bottom, AutoSize = false, Parent = form };
Action updateStatus = () =>
{
var rowCount = dg.Rows.Count - (dg.AllowUserToAddRows ? 1 : 0);
status.Text = "Rows: " + rowCount;
};
dg.UserDeletedRow += (sender, e) => updateStatus();
var timer = new Timer { Interval = 250, Enabled = true };
timer.Tick += (sender, e) => updateStatus();
Application.Run(form);
}
#if fBoundMode
class Data
{
public string Foo { get; set; }
public string Bar { get; set; }
}
#endif
}
}
I personally think the case you mentioned is not "by design", but rather than a result of some technical (implementation) difficulties. As being such, I would rather treat is as exception (with special code) rather than trying to generalize it. But that's up to you - I'm definitely not going to spend more time on this .
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With