J'ai un Listview avec 20 éléments. Je veux faire défiler la liste vue par programme.
ListView?.ScrollIntoView(ListView.Items[0])
fera défiler la liste jusqu'au premier élément.
ListView?.ScrollIntoView(ListView.Items.Count - 1)
fera défiler la liste jusqu'au bas de la page.
Cependant, je ne parviens pas à utiliser la même fonction pour faire défiler la liste à un élément au milieu.
Eg: ListView?.ScrollIntoView(ListView.Items[5])
devrait défiler et me prendre au 5ème élément de la liste. Mais au lieu de cela, cela me conduit au premier élément de la liste.
Serait bien si ce problème peut être obtenu avec une solution de contournement?
Je pense que ce que vous recherchez est une méthode pour réellement scroll un élément en haut de la ListView
.
Dans this post , j'ai créé une méthode d'extension qui fait défiler un élément particulier dans une variable ScrollViewer
.
L'idée est la même dans votre cas.
Vous devez d’abord rechercher l’instance ScrollViewer
dans votre ListView
, puis l’élément auquel vous souhaitez accéder, c’est-à-dire une ListViewItem
.
Voici une méthode d'extension pour obtenir la variable 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;
}
Une fois que j'ai obtenu l'instance ScrollViewer
, j'ai créé deux méthodes d'extension supplémentaires pour faire défiler un élément en fonction de son index ou de son objet attaché, respectivement. Puisque ListView
et GridView
partagent la même classe de base ListViewBase
. Ces deux méthodes d'extension devraient également fonctionner pour GridView
.
En gros, les méthodes vont d'abord trouver l'élément, s'il est déjà rendu, puis le faire défiler immédiatement. Si l'élément est null
, cela signifie que la virtualisation est activée et que l'élément n'a pas encore été réalisé. Donc, pour réaliser l’article en premier, appelez ScrollIntoViewAsync
(méthode basée sur la tâche pour encapsuler la ScrollIntoView
intégrée, comme pour ChangeViewAsync
, qui offre un code beaucoup plus propre), calculer la position et la sauvegarder. Depuis que je connais la position de défilement, je dois tout d’abord faire défiler l’élément jusqu’à sa position précédente instantanément (c’est-à-dire sans animation), puis enfin faire défiler jusqu’à la position souhaitée avec animation.
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;
}
}
Vous pouvez également utiliser la nouvelle surcharge de ScrollIntoView
en spécifiant le deuxième paramètre pour vous assurer que l'élément est aligné sur le bord supérieur; Cependant, cela n'a pas la transition de défilement régulier dans mes méthodes d'extension précédentes.
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);
ScrollIntoView amène simplement l'élément dans la vue, point, il ne défile pas jusqu'à une ligne.
Si vous l'appelez sur un membre situé en bas de la liste visible, l'élément défile jusqu'à ce que l'élément soit le dernier membre de la liste visible.
Si vous l'appelez sur un membre situé en haut de la liste, l'élément défile jusqu'à ce que l'élément soit le premier membre de la liste.
Si vous l'appelez sur un membre et qu'il est actuellement visible, aucune opération n'est effectuée.
Je résous ceci comme:
var sv = new ScrollViewerHelper().GetScrollViewer(listView);
sv.UpdateLayout();
sv.ChangeView(0, sv.ExtentHeight, null);
Et la méthode 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édits au propriétaire du code