Scroll a new item in a ItemsControl into view

Pieter Breed picture Pieter Breed · Oct 23, 2009 · Viewed 7.6k times · Source

I have an ItemsControl that is databound to a ObservableCollection. I have this method in the code behind which adds a new model to the list. I would then like to scroll the new item (at the bottom of the list) into view.

I think the size of the ItemsControl is not yet updated when I am querying the size, since the ActualHeight before and after the addition of the model is the same. The effect of this code is to scroll to a point slightly above the new item.

How would I know what the new ActualHeight is going to be?

Here is my code:

        ViewModel.CreateNewChapter();
        var height = DocumentElements.ActualHeight;
        var width = DocumentElements.ActualWidth;
        DocumentElements.BringIntoView(new Rect(0, height - 1, width, 1));

Answer

Thomas Levesque picture Thomas Levesque · Oct 23, 2009

I think you need to call BringIntoView on the item container, not the ItemsControl itself :

var container = DocumentElements.ItemContainerGenerator.ContainerFromItem(model) as FrameworkElement;
if (container != null)
    container.BringIntoView();

EDIT: actually this doesn't work, because at this point, the item container hasn't been generated yet... You could probably handle the StatusChanged event of the ItemContainerGenerator. I tried the following code :

public static class ItemsControlExtensions
{
    public static void BringItemIntoView(this ItemsControl itemsControl, object item)
    {
        var generator = itemsControl.ItemContainerGenerator;

        if (!TryBringContainerIntoView(generator, item))
        {
            EventHandler handler = null;
            handler = (sender, e) =>
            {
                switch (generator.Status)
                {
                    case GeneratorStatus.ContainersGenerated:
                        TryBringContainerIntoView(generator, item);
                        break;
                    case GeneratorStatus.Error:
                        generator.StatusChanged -= handler;
                        break;
                    case GeneratorStatus.GeneratingContainers:
                        return;
                    case GeneratorStatus.NotStarted:
                        return;
                    default:
                        break;
                }
            };

            generator.StatusChanged += handler;
        }
    }

    private static bool TryBringContainerIntoView(ItemContainerGenerator generator, object item)
    {
        var container = generator.ContainerFromItem(item) as FrameworkElement;
        if (container != null)
        {
            container.BringIntoView();
            return true;
        }
        return false;
    }
}

However it doesn't work either... for some reason, ContainerFromItem still returns null after the status changes to ContainersGenerated, and I have no idea why :S


EDIT : OK, I understand now... this was because of the virtualization : the containers are generated only when they need to be displayed, so no containers are generated for hidden items. If you switch virtualization off for the ItemsControl (VirtualizingStackPanel.IsVirtualizing="False"), the solution above works fine.