11

我有一个ItemsControl在 a 中显示它的项目ScrollViewer,并进行虚拟化。我正在尝试将其滚动ScrollViewer到它包含的(屏幕外,因此是虚拟的)项目。但是,由于该项目是虚拟化的,因此它实际上并不存在于屏幕上并且没有位置 (IIUC)。

我已经尝试BringIntoView过子元素,但它不会滚动到视图中。我也尝试使用TransformToAncestor,TransformBounds和手动执行此操作ScrollToVerticalOffset,但TransformToAncestor永远不会返回(我猜也是因为虚拟化,因为它没有位置,但我没有证据)并且它永远不会执行之后的代码。

是否可以通过虚拟化滚动到项目ItemsControl?如果是这样,怎么做?

4

5 回答 5

20

我一直在寻找一个带有 VirtualizingStackPanel 的 ItemsControl 来滚动到一个项目一段时间,并一直在寻找“使用 ListBox”的答案。我不想,所以我找到了一种方法。首先,您需要为您的 ItemsControl 设置一个控件模板,其中包含一个 ScrollViewer(如果您正在使用项目控件,您可能已经拥有该模板)。我的基本模板如下所示(包含在 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() 和其他常用的滚动方法来绕过您的项目。

于 2012-11-28T04:11:21.460 回答
9

在 .NET 源代码中四处寻找让我向您推荐使用 aListBox及其ScrollIntoView方法。此方法的实现依赖于一些internal方法VirtualizingPanel.BringIndexIntoView,例如强制在该索引处创建项目并滚动到它。许多这些机制是内部机制的事实意味着,如果你试图自己做这件事,你会度过一段糟糕的时光

(要使这带来的选择不可见,您可以重新模板ListBoxItems

于 2012-08-25T20:59:41.167 回答
7

我知道这是一个旧线程,但如果其他人(比如我)遇到它,我认为值得我刚刚发现的更新答案。

从 .NET Framework 4.5 开始,VirtualizingPanel有一个像魅力一样工作的公共BringIndexIntoViewPublic方法,包括基于像素的滚动。您必须对 your 进行子类化ItemsControl,或者使用VisualTreeHelper来找到它的 child VirtualizingPanel,但无论哪种方式,现在都很容易强制您ItemsControl精确滚动到特定的项目/索引。

于 2018-11-27T20:58:38.890 回答
2

使用@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 的人很有帮助,没有代码隐藏。

于 2018-04-18T20:48:30.843 回答
1

我知道我参加聚会已经很晚了,但希望这可以帮助其他人一起寻找解决方案......

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>
于 2015-03-17T18:53:22.407 回答