How to automatically scroll ScrollViewer - only if the user did not change scroll position How to automatically scroll ScrollViewer - only if the user did not change scroll position wpf wpf

How to automatically scroll ScrollViewer - only if the user did not change scroll position


You can use ScrollChangedEventArgs.ExtentHeightChange to know if a ScrollChanged is due to a change in the content or to a user action...When the content is unchanged, the ScrollBar position sets or unsets the auto-scroll mode.When the content has changed you can apply auto-scrolling.

Code behind:

    private Boolean AutoScroll = true;    private void ScrollViewer_ScrollChanged(Object sender, ScrollChangedEventArgs e)    {        // User scroll event : set or unset auto-scroll mode        if (e.ExtentHeightChange == 0)        {   // Content unchanged : user scroll event            if (ScrollViewer.VerticalOffset == ScrollViewer.ScrollableHeight)            {   // Scroll bar is in bottom                // Set auto-scroll mode                AutoScroll = true;            }            else            {   // Scroll bar isn't in bottom                // Unset auto-scroll mode                AutoScroll = false;            }        }        // Content scroll event : auto-scroll eventually        if (AutoScroll && e.ExtentHeightChange != 0)        {   // Content changed and auto-scroll mode set            // Autoscroll            ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight);        }    }


Here is an adaptation from several sources.

public class ScrollViewerExtensions    {        public static readonly DependencyProperty AlwaysScrollToEndProperty = DependencyProperty.RegisterAttached("AlwaysScrollToEnd", typeof(bool), typeof(ScrollViewerExtensions), new PropertyMetadata(false, AlwaysScrollToEndChanged));        private static bool _autoScroll;        private static void AlwaysScrollToEndChanged(object sender, DependencyPropertyChangedEventArgs e)        {            ScrollViewer scroll = sender as ScrollViewer;            if (scroll != null)            {                bool alwaysScrollToEnd = (e.NewValue != null) && (bool)e.NewValue;                if (alwaysScrollToEnd)                {                    scroll.ScrollToEnd();                    scroll.ScrollChanged += ScrollChanged;                }                else { scroll.ScrollChanged -= ScrollChanged; }            }            else { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); }        }        public static bool GetAlwaysScrollToEnd(ScrollViewer scroll)        {            if (scroll == null) { throw new ArgumentNullException("scroll"); }            return (bool)scroll.GetValue(AlwaysScrollToEndProperty);        }        public static void SetAlwaysScrollToEnd(ScrollViewer scroll, bool alwaysScrollToEnd)        {            if (scroll == null) { throw new ArgumentNullException("scroll"); }            scroll.SetValue(AlwaysScrollToEndProperty, alwaysScrollToEnd);        }        private static void ScrollChanged(object sender, ScrollChangedEventArgs e)        {            ScrollViewer scroll = sender as ScrollViewer;            if (scroll == null) { throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to ScrollViewer instances."); }            // User scroll event : set or unset autoscroll mode            if (e.ExtentHeightChange == 0) { _autoScroll = scroll.VerticalOffset == scroll.ScrollableHeight; }            // Content scroll event : autoscroll eventually            if (_autoScroll && e.ExtentHeightChange != 0) { scroll.ScrollToVerticalOffset(scroll.ExtentHeight); }        }    }

Use it in your XAML like so:

<ScrollViewer Height="230" HorizontalScrollBarVisibility="Auto" extensionProperties:ScrollViewerExtension.AlwaysScrollToEnd="True">    <TextBlock x:Name="Trace"/></ScrollViewer>


This code will automatically scroll to end when the content grows if it was previously scrolled all the way down.

XAML:

<Window x:Class="AutoScrollTest.Window1"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    Height="300" Width="300">    <ScrollViewer Name="_scrollViewer">        <Border BorderBrush="Red" BorderThickness="5" Name="_contentCtrl" Height="200" VerticalAlignment="Top">        </Border>    </ScrollViewer></Window>

Code behind:

using System;using System.Windows;using System.Windows.Threading;namespace AutoScrollTest{    public partial class Window1 : Window    {        public Window1()        {            InitializeComponent();            DispatcherTimer timer = new DispatcherTimer();            timer.Interval = new TimeSpan(0, 0, 2);            timer.Tick += ((sender, e) =>                {                    _contentCtrl.Height += 10;                    if (_scrollViewer.VerticalOffset == _scrollViewer.ScrollableHeight)                    {                        _scrollViewer.ScrollToEnd();                    }                });            timer.Start();        }    }}