Hacer ListView.ScrollIntoView Desplazar el elemento en el centro de ListView (C #)

ListView.ScrollIntoView(object) actualmente encuentra un objeto en ListView y se desplaza hacia él. Si se encuentra debajo del objeto al que se desplaza, desplaza el objeto a la fila superior. Si está posicionado arriba, lo desplaza a la vista en la fila inferior.

Me gustaría que el elemento se desplazara hacia el centro de mi vista de lista si actualmente no está visible. ¿Hay una manera fácil de lograr esto?

Es muy fácil hacer esto en WPF con un método de extensión que escribí. Todo lo que tiene que hacer para desplazar un elemento al centro de la vista es llamar a un único método.

Supongamos que tiene este XAML:

   

Su método ScrollIntoView será simplemente:

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

Obviamente, esto podría hacerse usando un ViewModel en lugar de hacer referencia explícita a los controles.

Lo siguiente es la implementación. Es muy general, maneja todas las posibilidades de IScrollInfo. Funciona con ListBox o cualquier otro ItemsControl, y funciona con cualquier panel, incluidos StackPanel, VirtualizingStackPanel, WrapPanel, DockPanel, Canvas, Grid, etc.

Simplemente ponga esto en un archivo .cs en algún lugar de su proyecto:

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

La excelente respuesta anterior de Ray Burns es específica de WPF.

Aquí hay una versión modificada que funciona en 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); } } 

Me parece recordar haber hecho algo así en algún momento. En lo que respecta a mi memoria, lo que hice fue:

  1. Determine si el objeto ya está visible o no.
  2. Si no es visible, obtenga el índice del objeto que desea y la cantidad de objetos que se muestran actualmente.
  3. (index you want) - (number of objects displayed / 2) debe ser la fila superior, de modo que desplácese hasta eso (asegurándose de que no se (index you want) - (number of objects displayed / 2) negativo, por supuesto)

Si miras la plantilla de un Listbox, es simplemente un scrollviewer con un itempresenter adentro. Deberá calcular el tamaño de sus artículos y usar el desplazamiento horizontal o vertical para colocar los elementos en su scrollviewer. El kit de herramientas de abril de silverlight tiene un método de extensión GetScrollHost al que puedes llamar en un cuadro de lista para obtener tu scrollviewer subyacente.

Una vez que tenga eso, puede usar el Offset Horizontal o Vertical actual como marco de referencia y mover su lista de manera acorde.

La muestra siguiente encontrará el scrollviewer de la vista de lista y lo usará para desplazar el elemento hacia mi centro de la vista de lista.

XAML:

           

Código detrás:

 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(); for (int i = 0; i < 100; i++) { Data.Add(i.ToString()); } DataContext = this; } public List 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; } } 

Encontré un enfoque adicional para resolver este problema, suponiendo que algunos de nosotros solo necesitáramos una forma de averiguar la Altura del elemento visual de acuerdo con la plantilla del elemento, esto ahorraría mucho tiempo.

Ok, supongo que tu XAML está estructurado de alguna manera similar a esto:

 :      :  

Y desea calcular para desplazarse hacia el centro pero no tiene idea de cuál es la altura actual de cada elemento en su cuadro de lista … así es como puede averiguarlo:

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

La excelente respuesta de Ray Burns arriba y el comentario de Fyodor Soikin:

“En realidad, no funciona con ningún otro ItemsControl … no funciona con DataGrid con la virtualización activada …”

Utilizar:

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

@todos: no puedo comentar en este momento, necesito 50 reputación