Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make ListView.ScrollIntoView Scroll the Item into the Center of the ListView (C#)

ListView.ScrollIntoView(object) currently finds an object in the ListView and scrolls to it. If you are positioned beneath the object you are scrolling to, it scrolls the object to the top row. If you are positioned above, it scrolls it into view at the bottom row.

I'd like to have the item be scrolled right into the center of my list view if it is currently not visible. Is there an easy way to accomplish this?

like image 483
Kirk Ouimet Avatar asked Jun 01 '10 02:06

Kirk Ouimet


2 Answers

It is very easy to do this in WPF with an extension method I wrote. All you have to do to scroll an item to the center of the view is to call a single method.

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 simply:

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

Obviously this could be done using a ViewModel as well rather than referencing the controls explicitly.

Following is the implementation. It is very general, handling all the IScrollInfo possibilities. It works with 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);   } } 
like image 74
Ray Burns Avatar answered Sep 19 '22 05:09

Ray Burns


Ray Burns' excellent answer above is WPF specific.

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);         }     }  
like image 35
Scrappydog Avatar answered Sep 22 '22 05:09

Scrappydog