我有ItemsControl
在ScrollViewer
中显示其项目,并进行虚拟化。我试图将ScrollViewer
滚动到它包含的(屏幕外,因此虚拟化)项目。但是,由于该项目是虚拟化的,因此它并不存在于屏幕上并且没有位置(IIUC)。
我在子元素上尝试了BringIntoView
,但它没有滚动到视图中。我还尝试使用TransformToAncestor
,TransformBounds
和ScrollToVerticalOffset
手动执行此操作,但TransformToAncestor
永远不会返回(我猜也是因为虚拟化,因为它没有位置,但是我没有证据证明它和代码从未执行过。
是否可以滚动到虚拟化ItemsControl
的项目?如果是这样,怎么样?
答案 0 :(得分:15)
我一直在寻找带有VirtualizingStackPanel的ItemsControl来滚动到一个项目一段时间,并一直找到“使用ListBox”的答案。我不想,所以我找到了办法。首先,您需要为其中包含ScrollViewer的ItemsControl设置一个控件模板(如果您使用的是项目控件,则可能已经拥有该模板)。我的基本模板如下所示(包含在ItemsControl的便捷样式中)
<Style x:Key="TheItemsControlStyle" TargetType="{x:Type ItemsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="{TemplateBinding Control.Padding}" BorderBrush="{TemplateBinding Border.BorderBrush}" Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True">
<ScrollViewer Padding="{TemplateBinding Control.Padding}" Focusable="False" HorizontalScrollBarVisibility="Auto">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
所以我基本上有一个带有滚动查看器的边框,它将包含我的内容 我的ItemsControl定义为:
<ItemsControl x:Name="myItemsControl" [..snip..] Style="{DynamicResource TheItemsControlStyle}" ScrollViewer.CanContentScroll="True" VirtualizingStackPanel.IsVirtualizing="True">
好的,现在好玩的部分。我已经创建了一个扩展方法来附加到任何ItemsControl以使其滚动到给定项目:
public static void VirtualizedScrollIntoView(this ItemsControl control, object item) {
try {
// this is basically getting a reference to the ScrollViewer defined in the ItemsControl's style (identified above).
// you *could* enumerate over the ItemsControl's children until you hit a scroll viewer, but this is quick and
// dirty!
// First 0 in the GetChild returns the Border from the ControlTemplate, and the second 0 gets the ScrollViewer from
// the Border.
ScrollViewer sv = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild((DependencyObject)control, 0), 0) as ScrollViewer;
// now get the index of the item your passing in
int index = control.Items.IndexOf(item);
if(index != -1) {
// since the scroll viewer is using content scrolling not pixel based scrolling we just tell it to scroll to the index of the item
// and viola! we scroll there!
sv.ScrollToVerticalOffset(index);
}
} catch(Exception ex) {
Debug.WriteLine("What the..." + ex.Message);
}
}
因此,使用扩展方法就可以像使用ListBox的伴随方法一样使用它:
myItemsControl.VirtualizedScrollIntoView(someItemInTheList);
效果很好!
请注意,您也可以调用sv.ScrollToEnd()和其他常用的滚动方法来绕过您的项目。
答案 1 :(得分:7)
在.NET源代码中,我建议您使用ListBox
及其ScrollIntoView
方法。此方法的实现依赖于一些internal
方法,如VirtualizingPanel.BringIndexIntoView
,这些方法强制在该索引处创建项目并滚动到该索引。许多机制都是内部的这一事实意味着,如果你试图在你自己的上做这件事,你将会有一段糟糕的时间。
(要使其带来的选择不可见,您可以重新模拟ListBoxItems
)
答案 2 :(得分:0)
我知道我参加派对已经很晚了,但希望这可以帮助其他人一起寻找解决方案......
int index = myItemsControl.Items.IndexOf(*your item*).FirstOrDefault();
int rowHeight = *height of your rows*;
myScrollView.ScrollToVerticalOffset(index*rowHeight);
//this will bring the given item to the top of the scrollViewer window
...我的XAML设置如下......
<ScrollViewer x:Name="myScrollView">
<ItemsControl x:Name="myItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<!-- data here -->
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
答案 3 :(得分:0)
使用@AaronCook示例,创建了一个适用于我的VirtualizingItemsControl的行为。这是代码:
public class ItemsControlScrollToSelectedBehavior : Behavior<ItemsControl>
{
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ItemsControlScrollToSelectedBehavior),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnSelectedItemsChanged)));
public object SelectedItem
{
get => GetValue(SelectedItemProperty);
set => SetValue(SelectedItemProperty, value);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ItemsControlScrollToSelectedBehavior target = (ItemsControlScrollToSelectedBehavior)d;
object oldSelectedItems = e.OldValue;
object newSelectedItems = target.SelectedItem;
target.OnSelectedItemsChanged(oldSelectedItems, newSelectedItems);
}
protected virtual void OnSelectedItemsChanged(object oldSelectedItems, object newSelectedItems)
{
try
{
var sv = VisualTreeHelper.GetChild(VisualTreeHelper.GetChild(AssociatedObject, 0), 0) as ScrollViewer;
// now get the index of the item your passing in
int index = AssociatedObject.Items.IndexOf(newSelectedItems);
if (index != -1)
{
sv?.ScrollToVerticalOffset(index);
}
}
catch
{
// Ignore
}
}
}
用法是:
<ItemsControl Style="{StaticResource VirtualizingItemsControl}"
ItemsSource="{Binding BoundItems}">
<i:Interaction.Behaviors>
<behaviors:ItemsControlScrollToSelectedBehavior SelectedItem="{Binding SelectedItem}" />
</i:Interaction.Behaviors>
</ItemsControl>
对那些喜欢使用行为和清理XAML,没有代码隐藏的人有帮助。
答案 4 :(得分:0)
我知道这是一个旧线程,但是如果遇到其他人(例如我),我认为值得我刚刚发现的更新答案。
从.NET Framework 4.5开始,VirtualizingPanel
有一个公用的BringIndexIntoViewPublic
方法,它的工作方式像一个超级按钮,包括基于像素的滚动。您必须将ItemsControl
进行子类化,或者使用VisualTreeHelper
来找到其子级VirtualizingPanel
,但是无论哪种方式,现在都非常容易将ItemsControl
强制精确滚动到特定项目/索引。