c# - div - scrollintoview offset
Windows 10 ScrollIntoView() no se desplaza a los elementos en el medio de una vista de lista (3)
Tengo una vista de lista con 20 elementos. Quiero desplazar la vista de lista mediante programación.
ListView?.ScrollIntoView(ListView.Items[0])
desplazará la vista de lista al primer elemento.
ListView?.ScrollIntoView(ListView.Items.Count - 1)
desplazará la vista de lista al final de la página.
Sin embargo, no puedo usar la misma función para desplazar la vista de lista a un elemento en el medio.
Eg: ListView?.ScrollIntoView(ListView.Items[5])
debería desplazarse y llevarme al quinto elemento de la lista. Pero en cambio me está llevando al primer elemento de la lista.
¿Sería genial si este comportamiento se puede lograr con alguna solución?
Creo que lo que está buscando es un método para
desplazar
un elemento a la parte superior de
ListView
.
En
esta publicación
, creé un método de extensión que se desplaza a un elemento particular dentro de un
ScrollViewer
.
La idea es la misma en tu caso.
Primero debe encontrar la instancia de
ScrollViewer
dentro de su
ListView
, luego el elemento real para desplazarse, es decir, un
ListViewItem
.
Aquí hay un método de extensión para obtener el
ScrollViewer
.
public static ScrollViewer GetScrollViewer(this DependencyObject element)
{
if (element is ScrollViewer)
{
return (ScrollViewer)element;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
Una vez que obtengo la instancia de
ScrollViewer
, he creado dos métodos de extensión más para desplazarse a un elemento en función de su índice u objeto adjunto, respectivamente.
Dado que
ListView
y
GridView
están compartiendo la misma clase base
ListViewBase
.
Estos dos métodos de extensión también deberían funcionar para
GridView
.
Actualizar
Básicamente, los métodos encontrarán primero el elemento, si ya está renderizado, luego desplácese hasta él de inmediato.
Si el elemento es
null
, significa que la virtualización está activada y el elemento aún no se ha realizado.
Entonces, para realizar primero el elemento, llame a
ScrollIntoViewAsync
(método basado en tareas para envolver el
ScrollIntoView
, igual que
ChangeViewAsync
, que ofrece un código mucho más limpio), calcule la posición y guárdelo.
Como ahora conozco la posición a la que desplazarme, primero necesito desplazar el elemento hasta su posición anterior al
instante
(es decir, sin animación), y luego, finalmente, desplazarme a la posición deseada con animación.
public async static Task ScrollToIndex(this ListViewBase listViewBase, int index)
{
bool isVirtualizing = default(bool);
double previousHorizontalOffset = default(double), previousVerticalOffset = default(double);
// get the ScrollViewer withtin the ListView/GridView
var scrollViewer = listViewBase.GetScrollViewer();
// get the SelectorItem to scroll to
var selectorItem = listViewBase.ContainerFromIndex(index) as SelectorItem;
// when it''s null, means virtualization is on and the item hasn''t been realized yet
if (selectorItem == null)
{
isVirtualizing = true;
previousHorizontalOffset = scrollViewer.HorizontalOffset;
previousVerticalOffset = scrollViewer.VerticalOffset;
// call task-based ScrollIntoViewAsync to realize the item
await listViewBase.ScrollIntoViewAsync(listViewBase.Items[index]);
// this time the item shouldn''t be null again
selectorItem = (SelectorItem)listViewBase.ContainerFromIndex(index);
}
// calculate the position object in order to know how much to scroll to
var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content);
var position = transform.TransformPoint(new Point(0, 0));
// when virtualized, scroll back to previous position without animation
if (isVirtualizing)
{
await scrollViewer.ChangeViewAsync(previousHorizontalOffset, previousVerticalOffset, true);
}
// scroll to desired position with animation!
scrollViewer.ChangeView(position.X, position.Y, null);
}
public async static Task ScrollToItem(this ListViewBase listViewBase, object item)
{
bool isVirtualizing = default(bool);
double previousHorizontalOffset = default(double), previousVerticalOffset = default(double);
// get the ScrollViewer withtin the ListView/GridView
var scrollViewer = listViewBase.GetScrollViewer();
// get the SelectorItem to scroll to
var selectorItem = listViewBase.ContainerFromItem(item) as SelectorItem;
// when it''s null, means virtualization is on and the item hasn''t been realized yet
if (selectorItem == null)
{
isVirtualizing = true;
previousHorizontalOffset = scrollViewer.HorizontalOffset;
previousVerticalOffset = scrollViewer.VerticalOffset;
// call task-based ScrollIntoViewAsync to realize the item
await listViewBase.ScrollIntoViewAsync(item);
// this time the item shouldn''t be null again
selectorItem = (SelectorItem)listViewBase.ContainerFromItem(item);
}
// calculate the position object in order to know how much to scroll to
var transform = selectorItem.TransformToVisual((UIElement)scrollViewer.Content);
var position = transform.TransformPoint(new Point(0, 0));
// when virtualized, scroll back to previous position without animation
if (isVirtualizing)
{
await scrollViewer.ChangeViewAsync(previousHorizontalOffset, previousVerticalOffset, true);
}
// scroll to desired position with animation!
scrollViewer.ChangeView(position.X, position.Y, null);
}
public static async Task ScrollIntoViewAsync(this ListViewBase listViewBase, object item)
{
var tcs = new TaskCompletionSource<object>();
var scrollViewer = listViewBase.GetScrollViewer();
EventHandler<ScrollViewerViewChangedEventArgs> viewChanged = (s, e) => tcs.TrySetResult(null);
try
{
scrollViewer.ViewChanged += viewChanged;
listViewBase.ScrollIntoView(item, ScrollIntoViewAlignment.Leading);
await tcs.Task;
}
finally
{
scrollViewer.ViewChanged -= viewChanged;
}
}
public static async Task ChangeViewAsync(this ScrollViewer scrollViewer, double? horizontalOffset, double? verticalOffset, bool disableAnimation)
{
var tcs = new TaskCompletionSource<object>();
EventHandler<ScrollViewerViewChangedEventArgs> viewChanged = (s, e) => tcs.TrySetResult(null);
try
{
scrollViewer.ViewChanged += viewChanged;
scrollViewer.ChangeView(horizontalOffset, verticalOffset, null, disableAnimation);
await tcs.Task;
}
finally
{
scrollViewer.ViewChanged -= viewChanged;
}
}
Un enfoque más simple, pero sin animación.
También puede usar la nueva sobrecarga de
ScrollIntoView
especificando el segundo parámetro para asegurarse de que el elemento esté alineado en el borde superior;
sin embargo, hacerlo no tiene la transición de desplazamiento suave en mis métodos de extensión anteriores.
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);
Resuelvo esto como:
var sv = new ScrollViewerHelper().GetScrollViewer(listView);
sv.UpdateLayout();
sv.ChangeView(0, sv.ExtentHeight, null);
Y el método GetScrollViewer:
public ScrollViewer GetScrollViewer(DependencyObject element)
{
if (element is ScrollViewer)
{
return (ScrollViewer)element;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var child = VisualTreeHelper.GetChild(element, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
créditos al dueño del código
ScrollIntoView solo trae el elemento a la vista, punto, no se desplaza a una fila.
Si lo llama a un miembro y está debajo de la parte inferior de la lista visible, se desplaza hacia abajo hasta que el elemento sea el último miembro de la lista visible.
Si lo llama a un miembro y está encima de la parte superior de la lista, se desplaza hacia arriba hasta que el elemento sea el primer miembro de la lista.
Si lo llama a un miembro y actualmente está visible, no funciona en absoluto.