Сделать ListView.ScrollIntoView Прокручивать элемент в центр ListView (С#)
ListView.ScrollIntoView(object)
в настоящее время находит объект в ListView
и прокручивает его. Если вы находитесь под объектом, к которому вы прокручиваете, он прокручивает объект до верхней строки. Если вы находитесь выше, он прокручивает его в поле зрения в нижней строке.
Я хотел бы, чтобы элемент был прокручен прямо в центр моего списка, если он в настоящее время не отображается. Есть ли простой способ достичь этого?
Ответы
Ответ 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);
}
}
Ответ 2
Отличный ответ 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);
}
}
Ответ 3
Кажется, я вспоминаю, как в какой-то момент я делал что-то подобное. Что касается моей памяти, то я сделал следующее:
- Определите, является ли объект уже видимым или нет.
- Если он не отображается, получите индекс нужного вам объекта и количество отображаемых объектов.
-
(index you want) - (number of objects displayed / 2)
должен быть верхней строкой, поэтому прокрутите список до этого (убедитесь, что вы, конечно, не отрицательно относитесь)
Ответ 4
Если вы посмотрите на шаблон списка, это просто scrollviewer с элементом itemspresenter внутри. Вам нужно будет рассчитать размер ваших предметов и использовать прокрутку по горизонтали или вертикально, чтобы расположить элементы в вашем scrollviewer. Инструментарий april silverlight имеет метод расширения GetScrollHost, который вы можете вызвать в списке, чтобы получить ваш основной scrollviewer.
После этого вы можете использовать текущий Horizontal или Вертикальный Смещение в виде рамки и перемещение вашего списка в соответствии с этим.
Ответ 5
Нижеприведенный образец найдет 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;
}
}
Ответ 6
Я нашел дополнительный подход для решения этой проблемы, предполагая, что некоторым из нас просто нужен способ узнать высоту визуального элемента в соответствии с шаблоном элемента, что значительно сэкономит ваше время.
Хорошо, я предполагаю, что ваш 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);
Ответ 7
Отличный ответ Рэя Бернса выше и комментарий Федора Сойкина:
"На самом деле, он не работает ни с одним другим элементом ItemsControl... не работает с DataGrid с включенной виртуализацией..."
Использование:
if (listBox.SelectedItem != null)
{
listBox.ScrollIntoView(listBox.SelectedItem);
listBox.ScrollToCenterOfView(listBox.SelectedItem);
}
@all: комментарий в настоящий момент не нужен 50 репутации