一尘不染

使ListView.ScrollIntoView滚动项目到ListView(C#)的中心

c#

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

如果当前不可见,我想使该项目滚动到列表视图的中心。有没有简单的方法可以做到这一点?


阅读 401

收藏
2020-05-19

共1个答案

一尘不染

在WPF中使用我编写的扩展方法很容易做到这一点。将项目滚动到视图中心所需要做的就是调用一个方法。

假设您有以下XAML:

<ListView x:Name="view" ItemsSource="{Binding Data}" /> 
<ComboBox x:Name="box"  ItemsSource="{Binding Data}"
                        SelectionChanged="ScrollIntoView" />

您的ScrollIntoView方法将很简单:

private void ScrollIntoView(object sender, SelectionChangedEventArgs e)
{
  view.ScrollToCenterOfView(box.SelectedItem);
}

显然,这也可以使用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);
  }
}
2020-05-19