Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine number of rows in DataGridView except of new row

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:

  1. Create WinForms VB project.
  2. Add DataGridView1.
  3. Add 2 text columns into the DataGridView1.
  4. Add the above event handler.
  5. Launch.
  6. Focus first column and type a.
  7. Immediately quit edit mode by pressing Esc.
  8. See the result. The above handler prints NewRowIndex=-1, but it should print number >= 0. Last IsNewRow value is false, but it should be true.
like image 566
miroxlav Avatar asked Oct 08 '15 15:10

miroxlav


3 Answers

This is by design (confirmed by the Microsoft). Workaround calculations are needed to achieve the result.

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

  • New row is present if and only if DataGridView.AllowUserToAddRows is true.
  • If new row is present, it is always positioned as the last record.

Let's calculate:

Row count except of new row

= 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.

like image 44
miroxlav Avatar answered Oct 31 '22 13:10

miroxlav


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.

  1. Set the DataGridView.AllowNew = False
    This should remove the new insert row

  2. Count DataGridView rows
    DataGridView.Rows.Count or DataGridView.RowCount (whichever is your requirement)

  3. 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:

  1. adding-a-new-row-to-the-datagridview-programmatically-datagridviewallowusertoaddrow-false

  2. how-to-cancel-an-add-new-by-pressing-escape

like image 69
haraman Avatar answered Oct 31 '22 14:10

haraman


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 .

like image 1
Ivan Stoev Avatar answered Oct 31 '22 13:10

Ivan Stoev