Get First Visible Item in WPF ListView C#

风流意气都作罢 提交于 2020-02-02 03:19:29

问题


Anyone know how to get a ListViewItem by grabbing the first visible item in the ListView? I know how to get the item at index 0, but not the first visible one.


回答1:


This was so painful to get working:

HitTestResult hitTest = VisualTreeHelper.HitTest(SoundListView, new Point(5, 5));
System.Windows.Controls.ListViewItem item = GetListViewItemFromEvent(null, hitTest.VisualHit) as System.Windows.Controls.ListViewItem;

And the function to get the list item:

System.Windows.Controls.ListViewItem GetListViewItemFromEvent(object sender, object originalSource)
    {
        DependencyObject depObj = originalSource as DependencyObject;
        if (depObj != null)
        {
            // go up the visual hierarchy until we find the list view item the click came from  
            // the click might have been on the grid or column headers so we need to cater for this  
            DependencyObject current = depObj;
            while (current != null && current != SoundListView)
            {
                System.Windows.Controls.ListViewItem ListViewItem = current as System.Windows.Controls.ListViewItem;
                if (ListViewItem != null)
                {
                    return ListViewItem;
                }
                current = VisualTreeHelper.GetParent(current);
            }
        }

        return null;
    }



回答2:


After trying to figure out something similar, I thought I would share my result here (as it seems easier than the other responses):

Simple visibility test I got from here.

private static bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
    if (!element.IsVisible)
        return false;

    Rect bounds =
        element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
    var rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}

Afterwards you can loop through the listboxitems and use that test to determine which are visible. Since the listboxitems are always ordered the same the first visible one in this list would be the first visible one to the user.

private List<object> GetVisibleItemsFromListbox(ListBox listBox, FrameworkElement parentToTestVisibility)
{
    var items = new List<object>();

    foreach (var item in PhotosListBox.Items)
    {
        if (IsUserVisible((ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item), parentToTestVisibility))
        {
            items.Add(item);
        }
        else if (items.Any())
        {
            break;
        }
    }

    return items;
}



回答3:


I can't believe there isn't an easier way...

http://social.msdn.microsoft.com/forums/en-US/wpf/thread/2d527831-43aa-4fd5-8b7b-08cb5c4ed1db




回答4:


We only have to calculate the offset of our listbox, and the first visible item will be the item at the index equal to the VerticalOffset...

        // queue is the name of my listbox
        VirtualizingStackPanel panel = VisualTreeHelper.GetParent(queue.Items[0] as ListBoxItem) as VirtualizingStackPanel;
        int offset = (int)panel.VerticalOffset;
        // then our desired listboxitem is:
        ListBoxItem item = queue.Items[offset] as ListBoxItem;

Hope this helps you . . .!




回答5:


The generality of WPF ListView seems to prevent the class from providing a property like WinForms' TopItem. However, if the instance is configured with a VirtualizingStackPanel, you can still query the topmost index directly. This avoids the searching and iteration required by other approaches. (The approach is based on this post.)

I think the hit-test method used in the accepted answer is more general, but if what you really want is a list index rather than a list item, then this might save an IndexOf call.

My app needed to save and restore the list position after making significant changes to the list contents. The code to set the top position (based on this post) is shown below as well. For convenience, these are implemented as extension methods.

public static class ListViewExtensions {
    public static int GetTopItemIndex(this ListView lv) {
        if (lv.Items.Count == 0) {
            return -1;
        }

        VirtualizingStackPanel vsp = lv.GetVisualChild<VirtualizingStackPanel>();
        if (vsp == null) {
            return -1;
        }
        return (int) vsp.VerticalOffset;
    }

    public static void ScrollToTopItem(this ListView lv, object item) {
        ScrollViewer sv = lv.GetVisualChild<ScrollViewer>();
        sv.ScrollToBottom();
        lv.ScrollIntoView(item);
    }
}

The extremely handy GetVisualChild method comes from an MSDN post:

public static class VisualHelper {
    public static T GetVisualChild<T>(this Visual referenceVisual) where T : Visual {
        Visual child = null;
        for (Int32 i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++) {
            child = VisualTreeHelper.GetChild(referenceVisual, i) as Visual;
            if (child != null && child is T) {
                break;
            } else if (child != null) {
                child = GetVisualChild<T>(child);
                if (child != null && child is T) {
                    break;
                }
            }
        }
        return child as T;
    }
}

Usage note on ScrollToTopItem: the ScrollToBottom() call takes effect immediately, but ScrollIntoView() seems to be deferred. So if you call GetTopItemIndex() immediately after ScrollToTopItem(), you'll get the index for an item near the bottom.

Update: just wanted to note that ScrollIntoView() takes 60-100ms on my system, for a list with fewer than 1,000 items. Sometimes it silently fails. I ended up creating a "scroll to index" method that uses sv.ScrollToVerticalOffset() instead.



来源:https://stackoverflow.com/questions/2926722/get-first-visible-item-in-wpf-listview-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!