[ACCEPTED]-Scrolling to an element of a virtualising ItemsControl-virtualizingstackpanel

Accepted answer
Score: 20

I've been looking at getting a ItemsControl 21 with a VirtualizingStackPanel to scroll 20 to an item for a while now, and kept finding 19 the "use a ListBox" answer. I didn't want 18 to, so I found a way to do it. First you 17 need to setup a control template for your 16 ItemsControl that has a ScrollViewer in 15 it (which you probably already have if you're 14 using an items control). My basic template 13 looks like the following (contained in a 12 handy style for the 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 11 basically got a border with a scroll viewer 10 thats going to contain my content.
My ItemsControl 9 is defined with:

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

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

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 5 in place you would use it just like ListBox's 4 companion method:

myItemsControl.VirtualizedScrollIntoView(someItemInTheList);

Works great!

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

Score: 10

Poking around in the .NET source code leads 9 me to recommend you the use of a ListBox and its 8 ScrollIntoView method. The implementation of this method 7 relies on a few internal methods like VirtualizingPanel.BringIndexIntoView which forces 6 the creation of the item at that index and 5 scrolls to it. The fact that many of those 4 mechanism are internal means that if you 3 try to do this on your own you're gonna have a bad time.

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

Score: 10

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

As of .NET Framework 6 4.5, VirtualizingPanel has a public BringIndexIntoViewPublic method which works like 5 a charm, including with pixel based scrolling. You'll 4 have to either sub-class your ItemsControl, or use the 3 VisualTreeHelper to find its child VirtualizingPanel, but either way it's 2 now very easy to force your ItemsControl to scroll precisely 1 to a particular item/index.

Score: 2

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

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 2 for those who like Behaviors and clean XAML, no 1 code-behind.

Score: 1

I know I'm pretty late to the party but 3 hopefully this may help someone else coming 2 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 1 my XAML is setup like this...

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

This is an old thread, but I would like 1 to suggest one way:

/// <summary>
/// Scrolls to the desired item
/// </summary>
/// <param name="control">ItemsControl</param>
/// <param name="item">item</param>
public static void ScrollIntoView(this ItemsControl control, Object item)
{
    FrameworkElement framework = control.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
    if (framework == null) { return; }
    framework.BringIntoView();
}

More Related questions