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(); } }}