Make ListView.ScrollIntoView将Item滚动到ListView的中心(C#)

Kir*_*met 52 c# wpf listview visual-studio

ListView.ScrollIntoView(object)当前在其中找到一个对象ListView并滚动到它.如果您位于要滚动的对象下方,则会将对象滚动到顶行.如果您位于上方,则会将其滚动到底行的视图中.

如果项目当前不可见,我想将项目滚动到列表视图的中心.有没有一种简单的方法来实现这一目标?

Ray*_*rns 78

使用我编写的扩展方法在WPF中执行此操作非常容易.将项目滚动到视图中心所需要做的就是调用单个方法.

假设你有这个XAML:

<ListView x:Name="view" ItemsSource="{Binding Data}" /> 
<ComboBox x:Name="box"  ItemsSource="{Binding Data}"
                        SelectionChanged="ScrollIntoView" /> 
Run Code Online (Sandbox Code Playgroud)

您的ScrollIntoView方法将简单地:

private void ScrollIntoView(object sender, SelectionChangedEventArgs e)
{
  view.ScrollToCenterOfView(box.SelectedItem);
} 
Run Code Online (Sandbox Code Playgroud)

显然,这也可以使用ViewModel来完成,而不是明确地引用控件.

以下是实施.它非常通用,处理所有IScrollInfo的可能性.它适用于ListBox或任何其他ItemsControl,适用于任何面板,包括StackPanel,VirtualizingStackPanel,WrapPanel,DockPanel,Canvas,Grid等.

只需将它放在项目中的某个.cs文件中:

public static class ItemsControlExtensions
{
  public static void ScrollToCenterOfView(this ItemsControl itemsControl, object item)
  {
    // Scroll immediately if possible
    if(!itemsControl.TryScrollToCenterOfView(item))
    {
      // Otherwise wait until everything is loaded, then scroll
      if(itemsControl is ListBox) ((ListBox)itemsControl).ScrollIntoView(item);
      itemsControl.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
        {
          itemsControl.TryScrollToCenterOfView(item);
        }));
    }
  }

  private static bool TryScrollToCenterOfView(this ItemsControl itemsControl, object item)
  {
    // Find the container
    var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
    if(container==null) return false;

    // Find the ScrollContentPresenter
    ScrollContentPresenter presenter = null;
    for(Visual vis = container; vis!=null && vis!=itemsControl; vis = VisualTreeHelper.GetParent(vis) as Visual)
      if((presenter = vis as ScrollContentPresenter)!=null)
        break;
    if(presenter==null) return false;

    // Find the IScrollInfo
    var scrollInfo = 
        !presenter.CanContentScroll ? presenter :
        presenter.Content as IScrollInfo ??
        FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
        presenter;

    // Compute the center point of the container relative to the scrollInfo
    Size size = container.RenderSize;
    Point center = container.TransformToAncestor((Visual)scrollInfo).Transform(new Point(size.Width/2, size.Height/2));
    center.Y += scrollInfo.VerticalOffset;
    center.X += scrollInfo.HorizontalOffset;

    // Adjust for logical scrolling
    if(scrollInfo is StackPanel || scrollInfo is VirtualizingStackPanel)
    {
      double logicalCenter = itemsControl.ItemContainerGenerator.IndexFromContainer(container) + 0.5;
      Orientation orientation = scrollInfo is StackPanel ? ((StackPanel)scrollInfo).Orientation : ((VirtualizingStackPanel)scrollInfo).Orientation;
      if(orientation==Orientation.Horizontal)
        center.X = logicalCenter;
      else
        center.Y = logicalCenter;
    }

    // Scroll the center of the container to the center of the viewport
    if(scrollInfo.CanVerticallyScroll) scrollInfo.SetVerticalOffset(CenteringOffset(center.Y, scrollInfo.ViewportHeight, scrollInfo.ExtentHeight));
    if(scrollInfo.CanHorizontallyScroll) scrollInfo.SetHorizontalOffset(CenteringOffset(center.X, scrollInfo.ViewportWidth, scrollInfo.ExtentWidth));
    return true;
  }

  private static double CenteringOffset(double center, double viewport, double extent)
  {
    return Math.Min(extent - viewport, Math.Max(0, center - viewport/2));
  }
  private static DependencyObject FirstVisualChild(Visual visual)
  {
    if(visual==null) return null;
    if(VisualTreeHelper.GetChildrenCount(visual)==0) return null;
    return VisualTreeHelper.GetChild(visual, 0);
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 实际上,它不适用于*any*其他`ItemsControl`.我没有测试所有可能性,但至少,它不适用于启用虚拟化的`DataGrid`.你看,如果目标项远离视口,`ContainerForItem`返回null,你的方法在那时放弃并返回false.并且安排它直到"在所有加载之后"也没有多大帮助,因为在滚动位置改变之前不会加载任何东西.(见下一条评论) (3认同)
  • 爱它。非常感谢!完美地工作。 (2认同)
  • @RayBurns此代码不适用于已关闭虚拟化的DataGrid。它甚至不会滚动。 (2认同)

Scr*_*dog 9

Ray Burns上面的优秀答案是WPF特有的.

这是一个适用于Silverlight的修改版本:

 public static class ItemsControlExtensions
    {
        public static void ScrollToCenterOfView(this ItemsControl itemsControl, object item)
        {
            // Scroll immediately if possible 
            if (!itemsControl.TryScrollToCenterOfView(item))
            {
                // Otherwise wait until everything is loaded, then scroll 
                if (itemsControl is ListBox) ((ListBox)itemsControl).ScrollIntoView(item);
                itemsControl.Dispatcher.BeginInvoke( new Action(() =>
                {
                    itemsControl.TryScrollToCenterOfView(item);
                }));
            }
        }

        private static bool TryScrollToCenterOfView(this ItemsControl itemsControl, object item)
        {
            // Find the container 
            var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
            if (container == null) return false;

            // Find the ScrollContentPresenter 
            ScrollContentPresenter presenter = null;
            for (UIElement vis = container; vis != null ; vis = VisualTreeHelper.GetParent(vis) as UIElement)
                if ((presenter = vis as ScrollContentPresenter) != null)
                    break;
            if (presenter == null) return false;

            // Find the IScrollInfo 
            var scrollInfo =
                !presenter.CanVerticallyScroll ? presenter :
                presenter.Content as IScrollInfo ??
                FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
                presenter;

            // Compute the center point of the container relative to the scrollInfo 
            Size size = container.RenderSize;
            Point center = container.TransformToVisual((UIElement)scrollInfo).Transform(new Point(size.Width / 2, size.Height / 2));
            center.Y += scrollInfo.VerticalOffset;
            center.X += scrollInfo.HorizontalOffset;

            // Adjust for logical scrolling 
            if (scrollInfo is StackPanel || scrollInfo is VirtualizingStackPanel)
            {
                double logicalCenter = itemsControl.ItemContainerGenerator.IndexFromContainer(container) + 0.5;
                Orientation orientation = scrollInfo is StackPanel ? ((StackPanel)scrollInfo).Orientation : ((VirtualizingStackPanel)scrollInfo).Orientation;
                if (orientation == Orientation.Horizontal)
                    center.X = logicalCenter;
                else
                    center.Y = logicalCenter;
            }

            // Scroll the center of the container to the center of the viewport 
            if (scrollInfo.CanVerticallyScroll) scrollInfo.SetVerticalOffset(CenteringOffset(center.Y, scrollInfo.ViewportHeight, scrollInfo.ExtentHeight));
            if (scrollInfo.CanHorizontallyScroll) scrollInfo.SetHorizontalOffset(CenteringOffset(center.X, scrollInfo.ViewportWidth, scrollInfo.ExtentWidth));
            return true;
        }

        private static double CenteringOffset(double center, double viewport, double extent)
        {
            return Math.Min(extent - viewport, Math.Max(0, center - viewport / 2));
        }

        private static DependencyObject FirstVisualChild(UIElement visual)
        {
            if (visual == null) return null;
            if (VisualTreeHelper.GetChildrenCount(visual) == 0) return null;
            return VisualTreeHelper.GetChild(visual, 0);
        }
    } 
Run Code Online (Sandbox Code Playgroud)