28 Comments

imageOne of our Windows 8 WinRT apps requires a ListView with two features:

  1. "Auto-stick". The ListView must automatically scroll to the bottom when a new message is added to it.
  2. "Scroll detection". User can disable the "Auto-stick" by manually scrolling the ListView. If the user scrolls the ListView to bottom, the "Auto-stick" is enabled, otherwise it's disabled.

Here's some guidance on how you can achieve these features in a WinRT app using C# and XAML.

Automatically scrolling ListView to bottom

The ListView can be programmatically scrolled using the ScrollIntoView-method. You can pass in the item and the ListView jumps to show it. Here's how you can automatically scroll the ListView to show the last item when the list's ItemsSource is an ObservableCollection and a new item is added to the collection:

1. Register a listener to collection's CollectionChanged-event:
private readonly ObservableCollection<string> items = new ObservableCollection<string>(); 
...

        private void MyListviewLoaded(object sender, RoutedEventArgs e) 
        { 
            this.MyListView.ItemsSource = items;

            items.CollectionChanged += (s, args) => ScrollToBottom(); 
        }
2. In the ScrollToBottom-method, set the ListView's SelectedIndex to the last item in the collection, then use ScrollIntoView to scroll to the SelectedItem:
private void ScrollToBottom() 
        { 
            var selectedIndex = MyListView.Items.Count - 1; 
            if (selectedIndex < 0) 
                return;

            MyListView.SelectedIndex = selectedIndex; 
            MyListView.UpdateLayout();

            MyListView.ScrollIntoView(MyListView.SelectedItem); 
        }

Another option (used by the WinRT XAML Toolkit) is to get the ScrollViewer-control from inside the ListView, and call its ScrollToVerticalOffset-method:

private void ScrollToBottom() 
        { 
            var scrollViewer = MyListView.GetFirstDescendantOfType<ScrollViewer>(); 
            scrollViewer.ScrollToVerticalOffset(scrollViewer.ScrollableHeight); 
        }

GetFirstDescendantOfType is part of the WinRT XAML toolkit. Here's the VisualTreeHelperExtensions -class from the toolkit which includes this extension method:

public static class VisualTreeHelperExtensions 
    { 
        public static T GetFirstDescendantOfType<T>(this DependencyObject start) where T : DependencyObject 
        { 
            return start.GetDescendantsOfType<T>().FirstOrDefault(); 
        }

        public static IEnumerable<T> GetDescendantsOfType<T>(this DependencyObject start) where T : DependencyObject 
        { 
            return start.GetDescendants().OfType<T>(); 
        }

        public static IEnumerable<DependencyObject> GetDescendants(this DependencyObject start) 
        { 
            var queue = new Queue<DependencyObject>(); 
            var count = VisualTreeHelper.GetChildrenCount(start);

            for (int i = 0; i < count; i++) 
            { 
                var child = VisualTreeHelper.GetChild(start, i); 
                yield return child; 
                queue.Enqueue(child); 
            }

            while (queue.Count > 0) 
            { 
                var parent = queue.Dequeue(); 
                var count2 = VisualTreeHelper.GetChildrenCount(parent);

                for (int i = 0; i < count2; i++) 
                { 
                    var child = VisualTreeHelper.GetChild(parent, i); 
                    yield return child; 
                    queue.Enqueue(child); 
                } 
            } 
        }

        public static T GetFirstAncestorOfType<T>(this DependencyObject start) where T : DependencyObject 
        { 
            return start.GetAncestorsOfType<T>().FirstOrDefault(); 
        }

        public static IEnumerable<T> GetAncestorsOfType<T>(this DependencyObject start) where T : DependencyObject 
        { 
            return start.GetAncestors().OfType<T>(); 
        }

        public static IEnumerable<DependencyObject> GetAncestors(this DependencyObject start) 
        { 
            var parent = VisualTreeHelper.GetParent(start);

            while (parent != null) 
            { 
                yield return parent; 
                parent = VisualTreeHelper.GetParent(parent); 
            } 
        }

        public static bool IsInVisualTree(this DependencyObject dob) 
        { 
            return Window.Current.Content != null && dob.GetAncestors().Contains(Window.Current.Content); 
        }

        public static Rect GetBoundingRect(this FrameworkElement dob, FrameworkElement relativeTo = null) 
        { 
            if (relativeTo == null) 
            { 
                relativeTo = Window.Current.Content as FrameworkElement; 
            }

            if (relativeTo == null) 
            { 
                throw new InvalidOperationException("Element not in visual tree."); 
            }

            if (dob == relativeTo) 
                return new Rect(0, 0, relativeTo.ActualWidth, relativeTo.ActualHeight);

            var ancestors = dob.GetAncestors().ToArray();

            if (!ancestors.Contains(relativeTo)) 
            { 
                throw new InvalidOperationException("Element not in visual tree."); 
            }

            var pos = 
                dob 
                    .TransformToVisual(relativeTo) 
                    .TransformPoint(new Point()); 
            var pos2 = 
                dob 
                    .TransformToVisual(relativeTo) 
                    .TransformPoint( 
                        new Point( 
                            dob.ActualWidth, 
                            dob.ActualHeight));

            return new Rect(pos, pos2); 
        } 
    }

Detecting when ListView is scrolled to the bottom

In our case the auto-stick is enabled or disabled based on where the user scrolls the ListView. This requires that we can detect when the ListView is scrolled (or actually, when the scrolling ends) and also we need to know if the ListView is scrolled to the bottom. In order to achieve these, we first need to access the ListView's ScrollViewer and then the ScrollViewer's vertical ScrollBar. We can use the GetFirstDescendantOfType and GetDescendantsOfType -extension methods from WinRT XAML Toolkit (shown above) to get these controls:

            var scrollViewer = MyListView.GetFirstDescendantOfType<ScrollViewer>();

            var scrollbars = scrollViewer.GetDescendantsOfType<ScrollBar>().ToList();

            var verticalBar = scrollbars.FirstOrDefault(x => x.Orientation == Orientation.Vertical);

Now that we have the vertical scroll bar, we can register an event handler to its Scroll-event:

if (verticalBar != null) 
                verticalBar.Scroll += BarScroll;

In the BarScroll-method we receive and event argument of type ScrollEventargs. This argument tells us when the scrolling has ended. After making sure that we have received the event we need, we can use the argument's NewValue-property to make sure if we have scrolled to the bottom:

void BarScroll(object sender, ScrollEventArgs e) 
        { 
            if (e.ScrollEventType != ScrollEventType.EndScroll) return;

            var bar = sender as ScrollBar; 
            if (bar == null) 
                return;

            System.Diagnostics.Debug.WriteLine("Scrolling ended");

            if (e.NewValue >= bar.Maximum) 
            { 
                System.Diagnostics.Debug.WriteLine("We are at the bottom"); 
                LockToBottom = true; 
            } 
            else 
            { 
                System.Diagnostics.Debug.WriteLine("We are away from the bottom"); 
                LockToBottom = false; 
            } 
        }

Now we just need to the edit the ScrollToBottom-method so that the automatic scrolling happens only if the LockToBottom-property is set to true:

private void ScrollToBottom() 
        { 
            if (!LockToBottom) 
                return;

            var selectedIndex = MyListView.Items.Count - 1; 
            if (selectedIndex < 0) 
                return;

            MyListView.SelectedIndex = selectedIndex; 
            MyListView.UpdateLayout();

            MyListView.ScrollIntoView(MyListView.SelectedItem); 
        }

Sample:

A sample app if available from the GitHub.

image

Add few messages to the list to see how it auto scrolls, because the “Auto Stick” is enabled by default. Then scroll the list up and auto stick is disabled.

Links:

WinRT XAML Toolkit

Comments

Comment by Alex

Great tutorial! thanks!

Alex
Comment by Ashwin N Bhanushali

How can use this To find Scroll End Event for GridView in Windows 8 metro apps?
I have grouped datasource for GridView.

Ashwin N Bhanushali
Comment by António

I think that have a problem... Doesn´t work with touch mode =

António
Comment by Micha

Scrollbar Scroll event only works for mouse scrolling. For touch scroll detection just use the ViewChanged event of the scrollViewer with its IsIntermediate flag. If the flag is False we do not scoll, if True we are scrolling. You might need this to work around a bug (COM Exception) when removing items from a virtualized listView while scrolling simultaniously.

Micha
Comment by New Bingo Sites

New Bingo Sites...

WinRT XAML: Automatically Scrolling ListView to Bottom and Detecting When ListView is Scrolled - Mikael Koskinen...

Comment by eso gold

eso gold...

it's very useful for me searching on this website!...

Comment by website design

website design...

Why don't we give this a try?...

Comment by runescape gold

runescape gold...

Im very pleased with your work....

Comment by fondgold reddit

fondgold reddit...

I'll take care of it ....

Comment by fondgold

fondgold...

No, I know....

Comment by wildstar gold

wildstar gold...

Leave me alone!...

Comment by wildstar gold

wildstar gold...

this is a very useful web site!...

Comment by wildstar gold

wildstar gold...

This is really a nice place....

Comment by buydiablo3golds

buydiablo3golds...

Be good!/be a man/be cool!...

Comment by fifa 15

fifa 15...

this site can help me to find some good suggestions!...

Comment by tibiamoney

tibiamoney...

Do not rush/push me....

Comment by archeage-store

archeage-store...

I was/will be there for you....

Comment by fifa15-coins

fifa15-coins...

Be good!/be a man/be cool!...

Comment by destiny-store

destiny-store...

You have a good taste....

Comment by fifa15-coins.com

fifa15-coins.com...

Thank you for your helping hand....

Comment by ArcheAge gold

ArcheAge gold...

Call it even....

Comment by Varun Agrawal

I can't find GetFirstDescendantByType on the ListView control. Have even installed the WinRTXAMLToolkit v1.7.3.