20個のアイテムを含むリストビューがあります。リストビューをプログラムでスクロールしたい。
ListView?.ScrollIntoView(ListView.Items[0])
リストビューを最初のアイテムまでスクロールします。
ListView?.ScrollIntoView(ListView.Items.Count - 1)
リストビューをページの一番下までスクロールします。
ただし、同じ機能を使用してリストビューを中央のアイテムにスクロールすることはできません。
Eg: ListView?.ScrollIntoView(ListView.Items[5])
スクロールして、リストの5番目の項目に移動する必要があります。しかし、代わりにそれは私をリストの最初の項目に連れて行ってくれます。
この動作が何らかの回避策で達成できれば素晴らしいでしょうか?
あなたが探しているのは、実際に要素をListView
の先頭にスクロールする方法だと思います。
この投稿 で、ScrollViewer
内の特定の要素にスクロールする拡張メソッドを作成しました。
考え方はあなたの場合も同じです。
最初にScrollViewer
内のListView
インスタンスを見つけ、次にスクロールする実際のアイテム、つまりListViewItem
を見つける必要があります。
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;
}
ScrollViewer
インスタンスを取得したら、インデックスまたはアタッチされたオブジェクトに基づいてアイテムにスクロールするための拡張メソッドをさらに2つ作成しました。 ListView
とGridView
は同じ基本クラスListViewBase
を共有しているため。これらの2つの拡張メソッドは、GridView
でも機能するはずです。
基本的に、メソッドは最初にアイテムを検索します(既にレンダリングされている場合)。次に、すぐにスクロールします。アイテムがnull
の場合、仮想化がオンになっていて、アイテムがまだ実現されていないことを意味します。したがって、最初に項目を実現するには、ScrollIntoViewAsync
(組み込みのScrollIntoView
をラップするタスクベースの方法、はるかにクリーンなコードを提供するChangeViewAsync
と同じ)を呼び出して、配置して保存します。スクロールする位置がわかったので、最初にアイテムを前の位置に瞬時に(つまりアニメーションなしで)スクロールして戻す必要があります。最後に、アニメーションで目的の位置までスクロールします。
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;
}
}
2番目のパラメーターを指定して、ScrollIntoView
の新しいオーバーロードを使用して、アイテムが上端に配置されていることを確認することもできます。ただし、これを行うと、以前の拡張メソッドではスムーズなスクロール遷移が得られません。
MyListView?.ScrollIntoView(MyListView.Items[5], ScrollIntoViewAlignment.Leading);
ScrollIntoViewは、アイテムをビュー、ピリオドに移動するだけで、行にスクロールしません。
メンバーでそれを呼び出し、それが表示リストの下部にある場合、アイテムが表示リストの最後のメンバーになるまで下にスクロールします。
メンバーでそれを呼び出し、それがリストの一番上にある場合、アイテムがリストの最初のメンバーになるまで上にスクロールします。
メンバーでそれを呼び出し、それが現在表示されている場合、それはまったく操作を行いません。
私はこれを次のように解決します:
var sv = new ScrollViewerHelper().GetScrollViewer(listView);
sv.UpdateLayout();
sv.ChangeView(0, sv.ExtentHeight, null);
そして、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;
}
コードの所有者へのクレジット