WPF getting the indices of ListView displayed items

I have a ListView that is show a number of items. In order to have a reliable way of ensuring that a particular item is not only scrolled into view but also in the center of the display I need to discover the indices of the visible items. After much searching I cannot find a way of discovering the indices of the items currently in view. It seems all the examples I find are for virtualized ListViews and mine is a default view and not virtualized. The ListView only displays a list, no icon type view.ing

Thanks,
Sid.
Sid PriceSoftware Systems Architect/DesignerAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Karrtik IyerSoftware ArchitectCommented:
Something like below, for the list view, have the scrollviewer's scroll changed event handled as shown below:
<ListView Height="96" HorizontalAlignment="Left" Margin="54,58,0,0" Name="ListView1" VerticalAlignment="Top" Width="413" ScrollViewer.ScrollChanged="ListView1_ScrollChanged">            

Open in new window

In the scroll changed, event as you scroll, you get the current item and the number of items shown, so the visible list view items indices shall start from the offset for a count of number of items.
    Private Sub ListView1_ScrollChanged(sender As System.Object, e As System.Windows.Controls.ScrollChangedEventArgs)
        Debug.WriteLine("Vertical offset: " & e.VerticalOffset) 'In the current view, index of the element which is first in the list view
        Debug.WriteLine("View port Height: " & e.ViewportHeight) 'Number of list view items shown in the current view.
    End Sub

Open in new window

Karrtik IyerSoftware ArchitectCommented:
Overall code would be something like below, which on button click gives the current items visible in listview.
Class MainWindow 
    Dim currentoffset As Integer = 0
    Dim numberofitemsshown As Integer = 0

  

    Private Sub ListView1_ScrollChanged(sender As System.Object, e As System.Windows.Controls.ScrollChangedEventArgs)
        currentoffset = e.VerticalOffset
        numberofitemsshown = e.ViewportHeight
        Debug.WriteLine("Vertical offset: " & e.VerticalOffset)
        Debug.WriteLine("View port Height: " & e.ViewportHeight)
    End Sub

    Private Sub Button1_Click_1(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles Button1.Click
        If numberofitemsshown = 0 Then
            Dim lvchild As Visual = VisualTreeHelper.GetChild(ListView1, 0)
            Dim anotherchild As Visual = VisualTreeHelper.GetChild(lvchild, 0)
            Dim sv As ScrollViewer = CType(anotherchild, ScrollViewer)
            numberofitemsshown = sv.ViewportHeight
        End If
        For i As Integer = 0 To ListView1.Items.Count - 1
            If (i >= currentoffset And i < currentoffset + numberofitemsshown) Then
                Debug.WriteLine(ListView1.Items(i).ToString() & " is visible")
            End If
        Next
    End Sub
End Class

Open in new window

XAML
<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListView Height="96" HorizontalAlignment="Left" Margin="54,58,0,0" Name="ListView1" VerticalAlignment="Top" Width="413" ScrollViewer.ScrollChanged="ListView1_ScrollChanged" >            
                <ListViewItem Content="A" />
                <ListViewItem Content="B" />
                <ListViewItem Content="C" />
                <ListViewItem Content="D" />
                <ListViewItem Content="E" />
                <ListViewItem Content="F" />
                <ListViewItem Content="G" />
                <ListViewItem Content="H" />
                <ListViewItem Content="I" />
                <ListViewItem Content="J" />            
        </ListView>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="430,224,0,0" Name="Button1" VerticalAlignment="Top" Width="75" />
    </Grid>
</Window>

Open in new window

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Sid PriceSoftware Systems Architect/DesignerAuthor Commented:
Many thanks I will give this a try and report back.
Sid
Rowby Goren Makes an Impact on Screen and Online

Learn about longtime user Rowby Goren and his great contributions to the site. We explore his method for posing questions that are likely to yield a solution, and take a look at how his career transformed from a Hollywood writer to a website entrepreneur.

Sid PriceSoftware Systems Architect/DesignerAuthor Commented:
While the code given above does not work for recovery of the item indices it did lead me to a solution that does work.

First there is no need to iterate over the visual tree to find the Viewport offset and height, both of these values are in the "e" argument (ScrollChangedEventArgs) of the Scroll Changed event, so they may be read directly in the event handler.

Second, the parsing of the visual tree given in the example assumes too much about that tree and a more generic iteration process is required in order to traverse the visual tree to find the "ActualHeight" value for a ListViewItem.

Having said that the given example did lead me to a solution so I will mark it as the solution.
Thanks,
Sid
Karrtik IyerSoftware ArchitectCommented:
Hi SidPrice,
Here are my comments as response for your last comment.
1> First there is no need to iterate over the visual tree to find the Viewport offset and height, both of these values are in the "e" argument (ScrollChangedEventArgs) of the Scroll Changed event, so they may be read directly in the event handler.
Karrtik: This is required because when no scrolling has happened, for the first time load, at that time ListView1_ScrollChanged is not raised and if we do not use visual tree helper, then the number of items shown in the UI cannot be known. hence it is only done for the first time when numberofitemsshown = 0 .
2> Second, the parsing of the visual tree given in the example assumes too much about that tree and a more generic iteration process is required in order to traverse the visual tree to find the "ActualHeight" value for a ListViewItem.
Karrtik: This is in a way contradictory to your first comment, as per your first comment there is no need to iterate over visual tree, in that case this code of visual tree need not be present at all, so where is the question this not being generic enough? :-)
Anyway, as I have mentioned above this code is required for the first time when no scrolling has happened.
As far as it not being generic, let us look at the code below to see where you expect it to be more generic and what would issues would happen because of below code.
        If numberofitemsshown = 0 Then
            Dim lvchild As Visual = VisualTreeHelper.GetChild(ListView1, 0)
            Dim anotherchild As Visual = VisualTreeHelper.GetChild(lvchild, 0)
            Dim sv As ScrollViewer = CType(anotherchild, ScrollViewer)
            numberofitemsshown = sv.ViewportHeight
        End If

Open in new window

Explanation of each line of code above:
1> Dim lvchild As Visual = VisualTreeHelper.GetChild(ListView1, 0), //This returns the border
Karrtik: We are trying to get the number of items shown in the list view when no scrolling has happened and your question is specific to List view, so I do not see any problem in the code above where we are trying get the first child of List view.
2>  Dim anotherchild As Visual = VisualTreeHelper.GetChild(lvchild, 0)// Border's child is scroll viewer
Karrtik: It is a microsoft documented approach where first child of listview shall return a Border object and from Border we can get the scroll viewer, so can you please tell me why you think this is going to be an issue? Or how in your case you would make it more generic?
3> Dim sv As ScrollViewer = CType(anotherchild, ScrollViewer)
Karrtik: As per MS documentation, first child of border of listview is the scroll viewer, hence we are type casting the first child of border as scroll viewer. Please help me understand how you plan to make it more generic, and what issue can happen with code above?
Sid PriceSoftware Systems Architect/DesignerAuthor Commented:
Many thanks for your detailed response, your help was appreciated the first time it was posted and it was marked as a solution.

I do not see any issues in using the event args, should I experience an issue your input I have no doubt will be invaluable.
Sid
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic.NET

From novice to tech pro — start learning today.