1

For some strange reason, a DataGridViewSelectedRowCollection is populated in reverse order from what is displayed in theDataGridView. But what is more puzzling is why there isn't a straightforward way of reversing the order to use in a foreach loop.

I would like to be able to use syntax as simple as this:

foreach (DataGridViewRow r in dataGridView1.SelectedRows.Reverse())

...but of course, that is not supported.*

So, currently I am using this monstrosity:

//reverse the default selection order:
IEnumerable<DataGridViewRow> properlyOrderedSelectedRows 
         = dataGridView1.SelectedRows.Cast<DataGridViewRow>().ToArray().Reverse();
foreach (DataGridViewRow r in properlyOrderedSelectedRows )
{
    MessageBox.Show( r.Cells["ID"].Value.ToString());
}

...which is terribly ugly and convoluted. (I realize I could use a reverse For loop, but I prefer the foreach for its readability.)

What am I missing here? Is there a simpler approach?

*Actually, I would have expected this version to work, according to the discussion here, since DataGridViewSelectedRowCollection implements IEnumerable; but it doesn't compile.

Community
  • 1
  • 1
kmote
  • 16,095
  • 11
  • 68
  • 91
  • 1
    Could you just write them out in reverse order? Loop from the end of the array, to the start? Avoiding the call to reverse all together? – Sam Plus Plus Sep 03 '15 at 15:47
  • @SamPlusPlus: I want to use a `foreach` loop, as explained above. – kmote Sep 03 '15 at 15:54
  • 3
    Under your constraints (honestly, not too clear to me; as far as I would personally rely on a reverse for loop) your approach seems fine (you might reduce the size of the code by removing properlyOrderedSelectedRows and adding the whole reversing in the foreach loop). At least, by assuming that SelectedRows is always provided in exact inverse order; otherwise you would have to rely on OrderBy by row index. – varocarbas Sep 03 '15 at 15:57

3 Answers3

3

I think you just need to cast in your for each loop like:

  foreach (DataGridViewRow row in dataGridView1.SelectedRows.Cast<DataGridViewRow>().Reverse()) {

  }

however this isn't as efficient even if it appears to be less code as it has to basically go through the enumerator forwards putting everything on a stack then pops everything back out in reverse order.

If you have a directly-indexable collection you should definitely use a for loop instead and enumerate over the collection in reverse order.

As mentioned here Possible to iterate backwards through a foreach?

Community
  • 1
  • 1
Sam Plus Plus
  • 4,381
  • 2
  • 21
  • 43
  • I'm aware of the efficiency trade-off, but I'm not terribly concerned about it. This is a SelectedRowCollection, and it will be rare when the user ever selects very many rows. But you did shrink my chain of methods by one, at least, so you win the prize. Thanks. – kmote Sep 03 '15 at 16:46
1

You could add the rows to a stack...

stack<DataGridViewRow> properlyOrderedSelectedRows  = new stack<DataGridViewRow>(dataGridView1.SelectedRows);

foreach (DataGridViewRow r in properlyOrderedSelectedRows )
{
    MessageBox.Show( r.Cells["ID"].Value.ToString());
}

Stack<T> has a constructor that accepts IEnumerable<T>

Ryan Searle
  • 1,597
  • 1
  • 19
  • 30
1

But what is more puzzling is why there isn't a straightforward way of reversing the order to use in a foreach loop.

I would like to be able to use syntax as simple as this:
...
...but of course, that is not supported.*

How about making it work yourself instead of all these pseudo witty statements, "monstrosities" and highly inefficient LINQ-es. All you need is to write a one liner function in some common place.

public static IEnumerable<DataGridViewRow> GetSelectedRows(this DataGridView source)
{
    for (int i = source.SelectedRows.Count - 1; i >= 0; i--)
        yield return source.SelectedRows[i];
}
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343