Robin
Robin

Reputation: 123

C# DataGridViewRow.Displayed bug for first row?

I have a Form where some Controls update according to the current displayed Rows (and their positions) in a DataGridView. So inside the dataGridView1.VerticalScrollBar.ValueChanged event I iterate through all rows and check wether they are displayed or not.

So here's the issue when scrolling back to top (by mousewheel):

After checking https://referencesource.microsoft.com, I realized that FirstDisplayedCell actually uses this.Rows.GetFirstRow(DataGridViewElementStates.Visible | DataGridViewElementStates.Frozen), which seems kind of incorrect, or am I wrong? How do I fix this, so that I get the correct displayed information / rectangle of the first row?

Thank you in advance!

Code:

public partial class Form1 : Form
{
    private List<Person> persons = new List<Person>()
    {
        {new Person("mike", 20) },
        {new Person("tim", 31) },
        {new Person("steven", 15) },
        {new Person("dave", 41) },
        {new Person("tom", 35) },
        {new Person("bob", 18) },
        {new Person("peter", 22) },
        {new Person("sarah", 55) },
        {new Person("robert", 69) },
        {new Person("andre", 23) },
    };
    public Form1()
    {
        this.InitializeComponent();
        this.dataGridView1.DataSource = new BindingSource(this.persons, null);

        if (typeof(DataGridView).GetProperty("VerticalScrollBar", BindingFlags.Instance | BindingFlags.NonPublic) is PropertyInfo pi &&
             pi.GetValue(this.dataGridView1, null) is ScrollBar sb)
        {
            sb.ValueChanged += this.Sb_ValueChanged;
        }
    }

    private void Sb_ValueChanged(object sender, EventArgs e)
    {
        int scrollValue = (sender as ScrollBar).Value;
        int index = this.dataGridView1.FirstDisplayedCell.RowIndex;
        int index2 = this.dataGridView1.FirstDisplayedScrollingRowIndex;
        DataGridViewRow row = this.dataGridView1.Rows[index];
        bool displayed = row.Displayed;
        Rectangle rect = this.dataGridView1.GetRowDisplayRectangle(index, false);
    }
}

Upvotes: 0

Views: 385

Answers (1)

TnTinMn
TnTinMn

Reputation: 11791

It appears that the state of the DataGridViewRow does not update until after the scroll event is finished processing. This is why the DataGridViewRow.Displayed property displays false when scrolling up to reveal a previously non displayed row.

You can use a bit of hack to queue a method to run right after the scroll event has finished by using Control.BeginInvoke.

Also, there is no reason to use Reflection to hack into the DataGridView's VerticalScrollBar. Use the DataGridView.Scroll Event instead.

The following is a modification of your code to use the DataGridView.Scroll event and BeginInvoke to display the row state and display rectangle values obtained within the scroll event and immediately after the event.

private void dataGridView1_Scroll(object sender, ScrollEventArgs e)
{
    if (e.ScrollOrientation == ScrollOrientation.VerticalScroll)
    {
        int scrollValue = e.NewValue;
        int index = this.dataGridView1.FirstDisplayedCell.RowIndex;
        int index2 = this.dataGridView1.FirstDisplayedScrollingRowIndex;
        DataGridViewRow row = this.dataGridView1.Rows[index];
        bool displayed = row.Displayed;
        Rectangle rect = this.dataGridView1.GetRowDisplayRectangle(index, false);

        Debug.WriteLine($"Scroll: index={index}, rect={rect}, state = {row.GetState(index)}");

        BeginInvoke(new Action(() =>
        {
            Rectangle r = dataGridView1.GetRowDisplayRectangle(index, false);
            Debug.WriteLine($"Invoked: index={index}, rect={r}, state = {row.GetState(index)}");
        }), null);
    }
}

Upvotes: 1

Related Questions