Es muy fácil de 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.
Suponga que tiene este XAML:
<ListView x:Name="view" ItemsSource="{Binding Data}" />
<ComboBox x:Name="box" ItemsSource="{Binding Data}"
SelectionChanged="ScrollIntoView" />
Su método ScrollIntoView será simplemente:
private void ScrollIntoView(object sender, SelectionChangedEventArgs e)
{
view.ScrollToCenterOfView(box.SelectedItem);
}
Obviamente, esto se podría hacer uso de un modelo de vista, así en lugar de hacer referencia a los controles de forma explícita.
La 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 que incluye StackPanel, VirtualizingStackPanel, WrapPanel, DockPanel, Lona, rejilla, etc.
sólo hay que poner 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);
}
}
Me encanta. ¡Muchas gracias! Funcionó perfectamente –
En realidad, no funciona con * cualquier * otro 'ItemsControl'. No probé todas las posibilidades, pero al menos no funciona con 'DataGrid' con la virtualización activada. Verá, en caso de que el elemento de destino esté demasiado lejos de la ventana gráfica, 'ContainerForItem' devuelve nulo, y su método se da por vencido en ese punto y devuelve falso. Y programarlo hasta "después de que todo se carga" tampoco ayuda mucho, porque nada se va a cargar hasta que la posición de desplazamiento cambie. (vea el siguiente comentario) –
Se puede agregar un caso especial para esto, tal como lo hizo para 'ListBox', pero estoy bastante seguro de que cualquier otra situación de virtualización arrojará el mismo resultado. ¿Alguna otra idea que sea poderosa "encapsulada" y que "cubra todas las posibilidades limpiamente"? –