【问题标题】:Get First Visible Item in WPF ListView C#在 WPF ListView C# 中获取第一个可见项
【发布时间】:2011-02-24 23:38:10
【问题描述】:

有谁知道如何通过抓取 ListView 中的第一个可见项来获取 ListViewItem?我知道如何获取索引 0 处的项目,但不是第一个可见的项目。

【问题讨论】:

    标签: c# wpf listview listviewitem


    【解决方案1】:

    工作太痛苦了:

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

    以及获取列表项的函数:

    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】:

      在尝试找出类似的东西后,我想我会在这里分享我的结果(因为它似乎比其他回复更容易):

      我从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);
      }
      

      之后,您可以遍历列表框项并使用该测试来确定哪些是可见的。由于列表框项的顺序始终相同,因此此列表中的第一个可见项将是用户第一个可见的项。

      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】:

        【讨论】:

        • 天哪,我想我只是吐在嘴里。使用 VisualTreeHelper 在相对点 0,0 处对孩子进行 HitTest 怎么样?
        【解决方案4】:

        我们只需要计算列表框的偏移量,第一个可见项将是索引处等于 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;
        

        希望这对您有所帮助。 . .!

        【讨论】:

        • 我收到错误消息:An unhandled exception of type 'System.InvalidCastException' occurred。我认为它出现在铸造 listBox.Items[0]
        • 我猜它在分组的情况下不起作用,但除此之外它是最快的方式(到目前为止)
        【解决方案5】:

        WPF ListView 的通用性似乎阻止了该类提供像 WinForms 的 TopItem 这样的属性。但是,如果实例配置了VirtualizingStackPanel,您仍然可以直接查询最顶层的索引。这避免了其他方法所需的搜索和迭代。 (该方法基于this post。)

        我认为接受的答案中使用的命中测试方法更通用,但如果您真正想要的是列表索引而不是列表项,那么这可能会节省 IndexOf 调用。

        在对列表内容进行重大更改后,我的应用需要保存和恢复列表位置。设置顶部位置的代码(基于this post)也如下所示。为方便起见,这些实现为扩展方法。

        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);
            }
        }
        

        非常方便的GetVisualChild方法来自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;
            }
        }
        

        ScrollToTopItem 的使用说明:ScrollToBottom() 调用立即生效,但ScrollIntoView() 似乎被推迟了。因此,如果您在 ScrollToTopItem() 之后立即调用 GetTopItemIndex(),您将获得靠近底部的项目的索引。

        更新: 只是想指出 ScrollIntoView() 在我的系统上需要 60-100 毫秒,对于少于 1,000 个项目的列表。有时它会默默地失败。我最终创建了一个使用sv.ScrollToVerticalOffset() 的“滚动到索引”方法。

        【讨论】:

          猜你喜欢
          • 2014-12-07
          • 2015-09-11
          • 2014-09-23
          • 2019-05-13
          • 2012-06-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多