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

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

如果当前不可见,我想将项目滚动到列表视图的中心。 有没有简单的方法来完成这个?

使用我写的扩展方法在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); } } 

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

我似乎记得在某个时候自己做这样的事情。 就我的记忆而言,我所做的是:

  1. 确定对象是否已经可见。
  2. 如果不可见,则获取所需对象的索引以及当前显示的对象的数量。
  3. (index you want) - (number of objects displayed / 2)应该是最上面一行,所以滚动到那个(当然,确保你没有消极)

如果你看一个ListBox的模板,它只是一个带有itemspresenter的scrollviewer。 您需要计算项目的大小,并使用水平或垂直滚动来将项目放置在滚动查看器中。 四月silverlight工具包有一个扩展方法GetScrollHost,你可以调用一个列表框来获得你的基础scrollviewer。

一旦你有了,你可以使用当前的水平或垂直偏移作为参考框架,并移动你的列表一致。

下面的示例将findlistview的滚动查看器,并使用它将项目滚动到列表视图的中间位置。

XAML:

 <Window x:Class="ScrollIntoViewTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="300" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <ListView Grid.Row="0" ItemsSource="{Binding Path=Data}" Loaded="OnListViewLoaded"/> <ComboBox Grid.Row="1" ItemsSource="{Binding Path=Data}" SelectionChanged="OnScrollIntoView" /> </Grid> </Window> 

代码后面:

 using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace ScrollIntoViewTest { public partial class Window1 : Window { public Window1() { InitializeComponent(); Data = new List<string>(); for (int i = 0; i < 100; i++) { Data.Add(i.ToString()); } DataContext = this; } public List<string> Data { get; set; } private void OnListViewLoaded(object sender, RoutedEventArgs e) { // Assumes that the listview consists of a scrollviewer with a border around it // which is the default. Border border = VisualTreeHelper.GetChild(sender as DependencyObject, 0) as Border; _scrollViewer = VisualTreeHelper.GetChild(border, 0) as ScrollViewer; } private void OnScrollIntoView(object sender, SelectionChangedEventArgs e) { string item = (sender as ComboBox).SelectedItem as string; double index = Data.IndexOf(item) - Math.Truncate(_scrollViewer.ViewportHeight / 2); _scrollViewer.ScrollToVerticalOffset(index); } private ScrollViewer _scrollViewer; } } 

我发现了一个解决这个问题的额外方法,假设我们中的一些人只是需要一种方法来根据项目模板找出视觉项目的高度,这将大大节省您的时间。

好的,我假设你的XAML在结构上类似于这个:

 : <Window.Resources> <DataTemplate x:Key="myTemplate"> <UserControls1:myControl DataContext="{Binding}" /> </DataTemplate> </Window.Resources> : <ListBox Name="myListBox" ItemTemplate="{StaticResource ResourceKey=myTemplate}" /> 

而你想计算为了滚动到中心,但你不知道什么是你的列表框中每个项目的当前高度..这是你如何能find:

 listBoxItemHeight = (double)((DataTemplate)FindResource("myTemplate")).LoadContent().GetValue(HeightProperty); 

雷·伯恩斯(Ray Burns)上面的出色答案和费奥多·索金(Fyodor Soikin)的评论:

“其实,它不能与任何其他ItemsControl …不能与DataGrid 打开虚拟化 …”

使用:

 if (listBox.SelectedItem != null) { listBox.ScrollIntoView(listBox.SelectedItem); listBox.ScrollToCenterOfView(listBox.SelectedItem); } 

@all:目前无法评论,需要50点声望