ListView.ScrollIntoView(object)
は、現在ListView
でオブジェクトを見つけてスクロールします。スクロール先のオブジェクトの下にいる場合、オブジェクトを一番上の行にスクロールします。あなたが上にいる場合、一番下の行にスクロールして表示されます。
現在表示されていない場合、アイテムをリストビューの中央にスクロールさせたいのですが。これを達成する簡単な方法はありますか?
私が書いた拡張メソッドを使用して、WPFでこれを行うのは非常に簡単です。アイテムをビューの中央にスクロールするために必要なことは、単一のメソッドを呼び出すことだけです。
このXAMLがあるとします:
<ListView x:Name="view" ItemsSource="{Binding Data}" />
<ComboBox x:Name="box" ItemsSource="{Binding Data}"
SelectionChanged="ScrollIntoView" />
ScrollIntoViewメソッドは次のようになります。
private void ScrollIntoView(object sender, SelectionChangedEventArgs e)
{
view.ScrollToCenterOfView(box.SelectedItem);
}
明らかに、コントロールを明示的に参照するのではなく、ViewModelを使用してこれを行うことができます。
以下は実装です。非常に一般的で、IScrollInfoのすべての可能性を処理します。 ListBoxまたはその他のItemsControlで動作し、StackPanel、VirtualizingStackPanel、WrapPanel、DockPanel、Canvas、Gridなどのパネルで動作します。
これをプロジェクトのどこかにある.csファイルに入れるだけです:
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);
}
}
上記のRay Burnsの優れた答えは、WPF固有です。
以下は、Silverlightで動作する修正バージョンです。
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( 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 (UIElement vis = container; vis != null ; vis = VisualTreeHelper.GetParent(vis) as UIElement)
if ((presenter = vis as ScrollContentPresenter) != null)
break;
if (presenter == null) return false;
// Find the IScrollInfo
var scrollInfo =
!presenter.CanVerticallyScroll ? 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.TransformToVisual((UIElement)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(UIElement visual)
{
if (visual == null) return null;
if (VisualTreeHelper.GetChildrenCount(visual) == 0) return null;
return VisualTreeHelper.GetChild(visual, 0);
}
}
以下のサンプルは、リストビューのスクロールビューアを見つけ、それを使用してアイテムをリストビューの中央までスクロールします。
XAML:
<Window x:Class="ScrollIntoViewTest.Window1"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView Grid.Row="0" ItemsSource="{Binding Path=Data}" Loaded="OnListViewLoaded"/>
<ComboBox Grid.Row="1" ItemsSource="{Binding Path=Data}" SelectionChanged="OnScrollIntoView" />
</Grid>
</Window>
コードビハインド:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ScrollIntoViewTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Data = new List<string>();
for (int i = 0; i < 100; i++)
{
Data.Add(i.ToString());
}
DataContext = this;
}
public List<string> Data { get; set; }
private void OnListViewLoaded(object sender, RoutedEventArgs e)
{
// Assumes that the listview consists of a scrollviewer with a border around it
// which is the default.
Border border = VisualTreeHelper.GetChild(sender as DependencyObject, 0) as Border;
_scrollViewer = VisualTreeHelper.GetChild(border, 0) as ScrollViewer;
}
private void OnScrollIntoView(object sender, SelectionChangedEventArgs e)
{
string item = (sender as ComboBox).SelectedItem as string;
double index = Data.IndexOf(item) - Math.Truncate(_scrollViewer.ViewportHeight / 2);
_scrollViewer.ScrollToVerticalOffset(index);
}
private ScrollViewer _scrollViewer;
}
}
上記のレイバーンズの優れた回答とフョードルソイキンのコメント:
「実際には、他のItemsControlでは機能しません... DataGrid with virtualization on ...では機能しません。」
使用する:
if (listBox.SelectedItem != null)
{
listBox.ScrollIntoView(listBox.SelectedItem);
listBox.ScrollToCenterOfView(listBox.SelectedItem);
}
@all:現時点ではコメントできません。50名の評価が必要です
この問題を解決するための追加のアプローチを見つけました。アイテムテンプレートに従ってビジュアルアイテムの高さを調べる方法が必要な場合、これにより時間を大幅に節約できます。
わかりました、あなたのXAMLはこれに似た何らかの形で構成されていると仮定します。
:
<Window.Resources>
<DataTemplate x:Key="myTemplate">
<UserControls1:myControl DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
:
<ListBox Name="myListBox" ItemTemplate="{StaticResource ResourceKey=myTemplate}" />
そして、中央にスクロールするために計算したいのですが、リストボックス内の各アイテムの現在の高さはわかりません。
listBoxItemHeight = (double)((DataTemplate)FindResource("myTemplate")).LoadContent().GetValue(HeightProperty);
いつか自分でこのようなことをしたことを思い出すようです。私の記憶に関して言えば、私がしたことは:
(index you want) - (number of objects displayed / 2)
を一番上の行にする必要があるので、そこまでスクロールします(もちろん、否定的にならないようにします)Listboxのテンプレートを見ると、itemspresenterが内部にある単純なスクロールビューアです。アイテムのサイズを計算し、scroll horizontally または vertically を使用してスクロールビューアーにアイテムを配置する必要があります。 4月のsilverlightツールキットには、基になるスクロールビューアーを取得するためにリストボックスで呼び出すことができる拡張メソッドGetScrollHostがあります。
それができたら、現在の Horizontal または Vertical Offsetを参照フレームとして使用し、リストをそれに応じて移動できます。