问题
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