Obtendrá el mismo efecto para una no virtualización ItemsControl
(ScrollViewer.CanContentScroll="False"
) que para una virtualización si se desplaza hacia abajo y luego selecciona el contenedor superior visible con el mouse. Esto también se puede hacer en código.
Cuando CanContentScroll
se configura como falso, la virtualización se desactiva para que todos los contenedores se generen en todo momento. Para obtener el contenedor visible superior, podemos iterar los contenedores desde la parte superior hasta que lleguemos al VerticalOffset
del ScrollViewer
. Una vez que lo tengamos simplemente podemos llamar al BringIntoView
y se alineará muy bien en la parte superior como lo haría si se utilizara la virtualización.
Ejemplo
<ListBox ItemsSource="{Binding MyCollection}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.ScrollChanged="listBox_ScrollChanged" >
llamada BringIntoView
en el recipiente superior visible en el controlador de eventos
private void listBox_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
ItemsControl itemsControl = sender as ItemsControl;
ScrollViewer scrollViewer = e.OriginalSource as ScrollViewer;
FrameworkElement lastElement = null;
foreach (object obj in itemsControl.Items)
{
FrameworkElement element = itemsControl.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
double offset = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)).Y + scrollViewer.VerticalOffset;
if (offset > e.VerticalOffset)
{
if (lastElement != null)
lastElement.BringIntoView();
break;
}
lastElement = element;
}
}
Para única lograr este efecto cuando se quiere llamar PageDown
, en un botón clic, por ejemplo, puedes crear una extensión thod para ListBox
llamado LogicalPageDown
.
listBox.LogicalPageDown();
ListBoxExtensions
public static class ListBoxExtensions
{
public static void LogicalPageDown(this ListBox listBox)
{
ScrollViewer scrollViewer = VisualTreeHelpers.GetVisualChild<ScrollViewer>(listBox);
ScrollChangedEventHandler scrollChangedHandler = null;
scrollChangedHandler = (object sender2, ScrollChangedEventArgs e2) =>
{
scrollViewer.ScrollChanged -= scrollChangedHandler;
FrameworkElement lastElement = null;
foreach (object obj in listBox.Items)
{
FrameworkElement element = listBox.ItemContainerGenerator.ContainerFromItem(obj) as FrameworkElement;
double offset = element.TransformToAncestor(scrollViewer).Transform(new Point(0, 0)).Y + scrollViewer.VerticalOffset;
if (offset > scrollViewer.VerticalOffset)
{
if (lastElement != null)
lastElement.BringIntoView();
break;
}
lastElement = element;
}
};
scrollViewer.ScrollChanged += scrollChangedHandler;
scrollViewer.PageDown();
}
}
me di cuenta en su pregunta que usted ya tiene la ScrollViewer
pero estoy añadiendo una implementación a GetVisualChild
si alguien viene a través de esta pregunta
public static T GetVisualChild<T>(DependencyObject parent) where T : Visual
{
T child = default(T);
int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numVisuals; i++)
{
Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
child = v as T;
if (child == null)
{
child = GetVisualChild<T>(v);
}
if (child != null)
{
break;
}
}
return child;
}
@Zmaster: Olvidé que 'Margen', etc. puede estar involucrado al encontrar la posición relativa del contenedor. Actualicé mi respuesta según corresponda –
Al final decidí utilizar ScrollToOffset() calculando el desplazamiento del objetivo con la suposición de que cada elemento tiene la misma altura (este es mi caso). Es más eficiente que repetir los elementos. Su solución, por otro lado, también funciona en diferentes alturas de elementos, así que lo acepté como la respuesta. – Zmaster