Make ListView.ScrollIntoView Scroll an item to the center of a ListView (C #)

ListView.ScrollIntoView(object) currently finds the object in the ListView and scrolls it. If you are below the object to which you are scrolling, it scrolls the object to the top line. If you are above, it scrolls it into the field of view on the bottom line.

I would like the item to scroll right to the center of my list if it is not currently displayed. Is there an easy way to achieve this?

+49
c # visual-studio listview wpf
Jun 01 '10 at 2:18
source share
7 answers

This is very easy to do in WPF using the extension method that I wrote. All you have to do to scroll the item to the center of the view is a single method call.

Suppose you have this XAML:

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

Your ScrollIntoView method will be simple:

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

Obviously, this can also be done using the ViewModel, and not directly refer to the controls.

Below is the implementation. It is very general, handling all the features of IScrollInfo. It works with a ListBox or any other ItemsControl and works with any panel, including StackPanel, VirtualizingStackPanel, WrapPanel, DockPanel, Canvas, Grid, etc.

Just put this in a .cs file somewhere in your project:

 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); } } 
+76
Jun 08 '10 at 23:10
source share

The excellent answer of Ray Burns above is the WPF specification.

Here is a modified version that works in 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); } } 
+9
Aug 11 '10 at 17:02
source share

I seem to recall how at one point I was doing something similar. As for my memory, I did the following:

  • Determine if the object is already visible or not.
  • If it is not displayed, get the index of the object you need and the number of objects displayed.
  • (index you want) - (number of objects displayed / 2) should be the top line, so scroll down to that (make sure that you, of course, are not negative)
+1
Jun 01 '10 at 2:27
source share

If you look at the list template, it's just a scrollviewer with an itemspresenter element inside. You will need to calculate the size of your items and use horizontal or vertical scrolling to position the elements in your scrollviewer. The april silverlight toolkit has an GetScrollHost extension method that you can call on the list to get your main scrollviewer.

After that, you can use the current Horizontal or Vertical Offset as a frame and move your list accordingly.

+1
Jun 08 '10 at 23:25
source share

The below sample will find the scrollviewer listview and use it to scroll the item to me in the middle of the list.

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> 

Code behind:

 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; } } 
+1
Jun 08 2018-10-10T00:
source share

I found an additional approach to solve this problem, suggesting that some of us just need a way to find out the height of the visual element according to the element template, which will save you a lot of time.

Ok, I assume your XAML is structured in some way similar:

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

And you want to calculate to scroll to the center, but you don’t know what the current height of each item in your list is. here is how you can find out:

 listBoxItemHeight = (double)((DataTemplate)FindResource("myTemplate")).LoadContent().GetValue(HeightProperty); 
+1
May 28 '12 at 4:54
source share

Ray Burns' excellent answer above and Fedor Soykin's comment:

"Actually, it doesn’t work with any other ItemsControl ... it doesn’t work with DataGrid with virtualization enabled ..."

Using:

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

@all: comment currently does not need 50 reputation

+1
Nov 28 '16 at 11:05
source share



All Articles