滚动到一个拥有虚拟化的元素的ItemsControl(Scrolling to an element

2019-07-31 04:22发布

我有一个ItemsControl ,显示在其项目ScrollViewer ,并执行虚拟化。 我想那滚动ScrollViewer它包含一个(屏幕外,因此虚拟化)项目。 然而,由于该项目是虚拟化的,它并没有真正存在在屏幕上,没有位置(IIUC)。

我曾尝试BringIntoView子元素,但它不会滚动进入视野。 我也尝试手动用做TransformToAncestorTransformBoundsScrollToVerticalOffset ,但TransformToAncestor不会返回(我猜是因为虚拟化的同时,因为它没有位置,但我没有这方面的证明)和代码它从未执行后。

是否有可能滚动到一个虚拟化项目ItemsControl ? 如果是这样,怎么样?

Answer 1:

在.NET源代码闲逛让我推荐你使用的ListBoxScrollIntoView方法。 此方法的实现依赖于一些internal的方法,如VirtualizingPanel.BringIndexIntoView这迫使该索引并滚动到它在该项目的创建。 许多这些机构都在内部,这意味着,如果你试图这样做你自己,你就要有一个坏的时间

(为了让选择这带来无形的,你可以retemplate的ListBoxItems



Answer 2:

我一直在寻找得到一个ItemsControl的一个VirtualizingStackPanel现在滚动到了一阵项目,并保持找到“使用列表框”的答案。 我不想,所以我找到了一种方法来做到这一点。 首先,你需要设置你的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()和其他常用的滚动方法来解决您的项目。



Answer 3:

我知道,我敢迟到了,但希望这可以帮助别人一起寻找解决方案来了...

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>


Answer 4:

使用@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,没有后台代码。



Answer 5:

我知道这是一个古老的线程,但万一别人(像我)遇到它,我想这将是值得的,我只是发现了一个更新的答案。

作为.NET框架4.5, VirtualizingPanel有一个公共BringIndexIntoViewPublic它就像一个魅力,其中包括使用基于像素的滚动方法。 你必须任何一个子类你ItemsControl ,或使用VisualTreeHelper找到其子VirtualizingPanel ,但无论哪种方式,它现在很容易迫使你ItemsControl精确滚动到一个特定的项目/索引。



文章来源: Scrolling to an element of a virtualising ItemsControl