wpf 滚动到虚拟化 ItemsControl 的元素

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12125078/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 05:06:14  来源:igfitidea点击:

Scrolling to an element of a virtualising ItemsControl

c#wpfitemscontrolvirtualizingstackpanel

提问by Seth Carnegie

I have a ItemsControlwhich displays its items in a ScrollViewer, and does virtualisation. I am trying to scroll that ScrollViewerto an (offscreen, hence virtualised) item it contains. However, since the item is virtualised, it doesn't really exist on the screen and has no position (IIUC).

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

I have tried BringIntoViewon the child element, but it doesn't scroll into view. I have also tried manually doing it with TransformToAncestor, TransformBoundsand ScrollToVerticalOffset, but TransformToAncestornever returns (I guess also because of the virtualisation, because it has no position, but I have no proof of that) and code after it never executes.

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

Is it possible to scroll to an item with a virtualising ItemsControl? If so, how?

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

采纳答案by H.B.

Poking around in the .NET source code leads me to recommend you the use of a ListBoxand its ScrollIntoViewmethod. The implementation of this method relies on a few internalmethods like VirtualizingPanel.BringIndexIntoViewwhich forces the creation of the item at that index and scrolls to it. The fact that many of those mechanism are internal means that if you try to do this on your own you're gonna have a bad time.

仔细研究 .NET 源代码,我建议您使用 aListBox及其ScrollIntoView方法。此方法的实现依赖于一些internal方法,例如VirtualizingPanel.BringIndexIntoView强制在该索引处创建项目并滚动到该项目。事实上,许多这些机制都是内部的,这意味着如果你试图自己做这件事,你会过得很糟糕

(To make the selection this brings with it invisible you can retemplate the ListBoxItems)

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

回答by Aaron Cook

I've been looking at getting a ItemsControl with a VirtualizingStackPanel to scroll to an item for a while now, and kept finding the "use a ListBox" answer. I didn't want to, so I found a way to do it. First you need to setup a control template for your ItemsControl that has a ScrollViewer in it (which you probably already have if you're using an items control). My basic template looks like the following (contained in a handy style for the ItemsControl)

我一直在寻找一个带有 VirtualizingStackPanel 的 ItemsControl 来滚动到一个项目一段时间,并一直在寻找“使用列表框”的答案。我不想,所以我找到了一种方法来做到这一点。首先,您需要为 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>

So I've basically got a border with a scroll viewer thats going to contain my content.
My ItemsControl is defined with:

所以我基本上有一个带有滚动查看器的边框,它将包含我的内容。
我的 ItemsControl 定义为:

<ItemsControl x:Name="myItemsControl" [..snip..] Style="{DynamicResource TheItemsControlStyle}"  ScrollViewer.CanContentScroll="True" VirtualizingStackPanel.IsVirtualizing="True">

Ok now for the fun part. I've created a extension method to attach to any ItemsControl to get it to scroll to the given item:

好的,现在是有趣的部分。我创建了一个扩展方法来附加到任何 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);
        }
    }

So with the extension method in place you would use it just like ListBox's companion method:

因此,使用扩展方法后,您可以像使用 ListBox 的伴随方法一样使用它:

myItemsControl.VirtualizedScrollIntoView(someItemInTheList);

Works great!

效果很好!

Note that you can also call sv.ScrollToEnd() and the other usual scrolling methods to get around your items.

请注意,您还可以调用 sv.ScrollToEnd() 和其他常用的滚动方法来绕过您的项目。

回答by Peter Moore

I know this is an old thread, but in case someone else (like me) comes across it, I figured it would be worth an updated answer that I just discovered.

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

As of .NET Framework 4.5, VirtualizingPanelhas a public BringIndexIntoViewPublicmethod which works like a charm, including with pixel based scrolling. You'll have to either sub-class your ItemsControl, or use the VisualTreeHelperto find its child VirtualizingPanel, but either way it's now very easy to force your ItemsControlto scroll precisely to a particular item/index.

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

回答by Chris D

I know I'm pretty late to the party but hopefully this may help someone else coming along looking for the solution...

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

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

... and my XAML is setup like this...

...我的 XAML 是这样设置的...

<ScrollViewer x:Name="myScrollView">
    <ItemsControl x:Name="myItemsControl">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <!-- data here -->
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

回答by TravisWhidden

Using @AaronCook example, Created a behavior that works for my VirtualizingItemsControl. Here is the code for that:

使用@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
        }
    }
}

and usage is:

用法是:

<ItemsControl Style="{StaticResource VirtualizingItemsControl}"                      
                  ItemsSource="{Binding BoundItems}">
        <i:Interaction.Behaviors>
            <behaviors:ItemsControlScrollToSelectedBehavior SelectedItem="{Binding SelectedItem}" />
        </i:Interaction.Behaviors>
    </ItemsControl>

Helpful for those who like Behaviors and clean XAML, no code-behind.

对那些喜欢行为和干净的 XAML 的人很有帮助,没有代码隐藏。