3

I have a DataGrid of variable dimensions dependent upon screen-res. I need to know how many rows are visible to the user. Here's my code:

uint VisibleRows = 0;
var TicketGrid = (DataGrid) MyWindow.FindName("TicketGrid");

foreach(var Item in TicketGrid.Items) {
    var Row = (DataGridRow) TicketGrid.ItemContainerGenerator.ContainerFromItem(Item);
    if(Row != null && Row.IsVisible) {
        VisibleRows++;
    }
}

I'm using the following code to test the vars:

MessageBox.Show(String.Format("{0} of {1} rows visible", VisibleRows, TicketGrid.Items.Count));
  • When there are no rows in the grid, it correctly shows 0 of 0 rows visible:

  • When there is 1 row in the grid, it correctly shows 1 of 1 rows visible:

  • When there are 9 rows in the grid, it correctly shows 9 of 9 rows visible:

  • The next row is "half-visible", so I'll count it showing 10 of 10 rows visible as correct:

  • However the next row to be added is apparently visible, incorrectly showing 11 of 11 rows visible:

  • Rows added after this are correct (bar the stray 1), e.g. 11 of 18 rows visible:


I can't just - 1, because it's only incorrect after a certain number have been added. I can't check > 10, because the dimensions are variable.

How can I fix this?

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
  • the row after the half visible row is **not** visible – DrKoch Dec 18 '14 at 14:10
  • 1
    @DrKoch That's the whole point...... – Danny Beckett Dec 18 '14 at 14:11
  • Your text says "apparently visible" - very confusing – DrKoch Dec 18 '14 at 14:15
  • @DrKoch C# says it's visible; it isn't. It's "apparently" visible, but sorry for the confusion! :) – Danny Beckett Dec 18 '14 at 14:16
  • Did you check if Row.IsVisible needs some time (e.g. a screen refresh) before it has the correct value? – DrKoch Dec 18 '14 at 14:20
  • @DrKoch Row 11 is always showing as visible, regardless of how long I wait before clicking the button that shows the `MessageBox` :( – Danny Beckett Dec 18 '14 at 14:23
  • You say "VisibleRows++" happens in your click-show-Messagebox? – DrKoch Dec 18 '14 at 14:25
  • @DrKoch No. Also if you have a look at the last screenshot, with 18 rows, it's had a screen refresh 7 extra times, but still shows that 11 rows are visible, not 10. – Danny Beckett Dec 18 '14 at 14:29
  • Forgive me if this is the first place you looked but have you tried this [ItemsPresenter.VisualChildrenCount](http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.visualchildrencount(v=vs.110).aspx) another thing worth looking at is when the virtualization kicks in so you can then determine when the you could do the unfortunate `-1`. HTH – XAMlMAX Dec 18 '14 at 14:39
  • @XAMlMAX I'm not sure how I can use that in my code? I tried `TicketGrid.VisualChildrenCount` and `TicketGrid.GetVisualChild`, but neither show up in Intellisense as valid. Are you able to give me an example at all please? Thanks! Aside: I wondered whether the header row had anything to do with this, but with no rows added to the DG it correctly shows *0 of 0*. All a bit odd! – Danny Beckett Dec 18 '14 at 14:54
  • I just saw that the VisualChildrenCount is a Protected Property, sorry. mea culpa. I'll keep looking for a solution though, this time reading the actual content. – XAMlMAX Dec 18 '14 at 15:00
  • @XAMlMAX Thanks for trying anyway! I also tried changing `if(Row != null && Row.IsVisible) {` to `if(Row != null && Row.IsVisible && Row.IsMeasureValid && Row.IsArrangeValid && Row.IsDescendantOf(TicketGrid)) {` without success. It has the same results. – Danny Beckett Dec 18 '14 at 15:01
  • @XAMlMAX Upon checking `Row.ActualHeight`, rows 1-10 show `30`. However row 11 *also* shows `30`. How is this possible? Row 12 onwards result in a `NullRefException`. – Danny Beckett Dec 18 '14 at 15:06
  • That looks to me like a way of forcing the `Virtualization`, what I mean by that is the `ItemContainerGenerator` will create additional containers to display outside of the visible area so the generation will not start when user actually scrolls to the not yet rendered area, if that makes sense. – XAMlMAX Dec 18 '14 at 15:08
  • @XAMlMAX That makes sense. I wonder how to overcome it though... – Danny Beckett Dec 18 '14 at 15:11
  • 1
    I've found a solution! Just coding it up now! :) – Danny Beckett Dec 18 '14 at 15:19
  • There is also this [VisrtualizingStackPanel](http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel.aspx) which has property Called `IsVirtualizing`. If this actually works then you could get the rendered size of the `DataGrid` or `VirtualizingStackPanel`, multiply the number of items by their height, check if `IsVirtualizing` and then do `-1`? I can't join the chat sorry I am going home in 40 mins. – XAMlMAX Dec 18 '14 at 15:19
  • @XAMlMAX Check out the answer if you wish, it's there now! Your train of thought helped me get to this, so thanks! – Danny Beckett Dec 18 '14 at 15:31

1 Answers1

5

Here's what finally worked for me:

uint VisibleRows = 0;
var TicketGrid = (DataGrid) MyWindow.FindName("TicketGrid");

foreach(var Item in TicketGrid.Items) {
    var Row = (DataGridRow) TicketGrid.ItemContainerGenerator.ContainerFromItem(Item);

    if(Row != null) {
        /*
           This is the magic line! We measure the Y position of the Row, relative to
           the TicketGrid, adding the Row's height. If it exceeds the height of the
           TicketGrid, it ain't visible!
        */

        if(Row.TransformToVisual(TicketGrid).Transform(new Point(0, 0)).Y + Row.ActualHeight >= TicketGrid.ActualHeight) {
            break;
        }

        VisibleRows++;
    }
}

Upto and including row 9 shows 9 of 9 visible. The "half-visible" row 10 results in 9 of 10 visible. It's actually better for my purposes for this not to count as a visible row, so this'll do for me! :)

Note: if you're reusing my code without using the break, any invisible rows after the offending row will throw a NullRefException.

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134