ListView.ScrollIntoView(object)
当前在ListView
中找到一个对象并滚动到它。如果您位于要滚动的对象下方,则会将对象滚动到顶行。如果您位于上方,则会将其滚动到底行的视图中。
如果项目当前不可见,我想将项目滚动到列表视图的中心。有没有一种简单的方法可以实现这一目标?
答案 0 :(得分:78)
使用我编写的扩展方法在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);
}
}
答案 1 :(得分:9)
以下是适用于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);
}
}
答案 2 :(得分:1)
我似乎记得在某些时候自己做过这样的事情。就我的记忆而言,我所做的是:
(index you want) - (number of objects displayed / 2)
应该是最上面一行,所以请滚动到那个(当然,确保你不会消极)答案 3 :(得分:1)
如果你看一下Listbox的模板,它只是一个里面有itemspresenter的scrollviewer。您需要计算商品的尺寸,并使用滚动horizontally或vertically来定位滚动查看器中的商品。 april silverlight工具包有一个扩展方法GetScrollHost,您可以在列表框上调用它来获取底层的滚动查看器。
完成后,您可以使用当前的Horizontal或Vertical偏移量作为参照系,并按顺序移动列表。
答案 4 :(得分:1)
下面的示例将找到listview的scrollviewer,并使用它将项目滚动到listview中间。
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;
}
}
答案 5 :(得分:1)
我找到了另一种方法来解决这个问题,假设我们中的一些人只需要根据项目模板找出视觉项目的高度,这将大大节省您的时间。
好的,我认为您的XAML的结构与此类似:
:
<Window.Resources>
<DataTemplate x:Key="myTemplate">
<UserControls1:myControl DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
:
<ListBox Name="myListBox" ItemTemplate="{StaticResource ResourceKey=myTemplate}" />
你想要计算才能滚动到中心,但你不知道当前的高度是多少 列表框中的每个项目..这是你可以找到的方法:
listBoxItemHeight = (double)((DataTemplate)FindResource("myTemplate")).LoadContent().GetValue(HeightProperty);
答案 6 :(得分:1)
&#34;实际上,它不能与任何其他ItemsControl一起使用... DataGrid 与虚拟化已打开无法合作。 ..&#34;
使用:
if (listBox.SelectedItem != null)
{
listBox.ScrollIntoView(listBox.SelectedItem);
listBox.ScrollToCenterOfView(listBox.SelectedItem);
}
@all:目前无法评论,需要50点声望