如何自动滚动ScrollViewer – 仅当用户没有更改滚动位置时

我想在包装ContentControlScrollViewer中创建以下行为:
ContentControl高度增加时, ScrollViewer应自动滚动到结尾。 使用ScrollViewer.ScrollToEnd()很容易实现。
但是,如果用户使用滚动条,则不应再进行自动滚动。 这类似于VS输出窗口中发生的情况。

问题是要知道由于用户滚动而发生滚动的时间以及由于内容大小发生变化而发生滚动的时间。 我尝试使用ScrollChangedEventArgsScrollChangedEvent ,但无法使其工作。

理想情况下,我不想处理所有可能的鼠标和键盘事件。

如果内容先前已向下滚动,则此代码将自动滚动到内容增长时结束。

XAML:

       

代码背后:

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

您可以使用ScrollChangedEventArgs.ExtentHeightChange来了解ScrollChanged是由于内容更改还是用户操作更改…当内容未更改时,ScrollBar位置设置或取消设置自动滚动模式。 内容更改后,您可以应用自动滚动。

代码背后:

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

以下是几个来源的改编。

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

像你这样在你的XAML中使用它:

    

这是我用过的方法,效果很好。 基于两个依赖属性。 它避免了代码隐藏和定时器,如另一个答案中所示。

 public static class ScrollViewerEx { public static readonly DependencyProperty AutoScrollProperty = DependencyProperty.RegisterAttached("AutoScrollToEnd", typeof(bool), typeof(ScrollViewerEx), new PropertyMetadata(false, HookupAutoScrollToEnd)); public static readonly DependencyProperty AutoScrollHandlerProperty = DependencyProperty.RegisterAttached("AutoScrollToEndHandler", typeof(ScrollViewerAutoScrollToEndHandler), typeof(ScrollViewerEx)); private static void HookupAutoScrollToEnd(DependencyObject d, DependencyPropertyChangedEventArgs e) { var scrollViewer = d as ScrollViewer; if (scrollViewer == null) return; SetAutoScrollToEnd(scrollViewer, (bool)e.NewValue); } public static bool GetAutoScrollToEnd(ScrollViewer instance) { return (bool)instance.GetValue(AutoScrollProperty); } public static void SetAutoScrollToEnd(ScrollViewer instance, bool value) { var oldHandler = (ScrollViewerAutoScrollToEndHandler)instance.GetValue(AutoScrollHandlerProperty); if (oldHandler != null) { oldHandler.Dispose(); instance.SetValue(AutoScrollHandlerProperty, null); } instance.SetValue(AutoScrollProperty, value); if (value) instance.SetValue(AutoScrollHandlerProperty, new ScrollViewerAutoScrollToEndHandler(instance)); } 

这使用定义为的处理程序。

 public class ScrollViewerAutoScrollToEndHandler : DependencyObject, IDisposable { readonly ScrollViewer m_scrollViewer; bool m_doScroll = false; public ScrollViewerAutoScrollToEndHandler(ScrollViewer scrollViewer) { if (scrollViewer == null) { throw new ArgumentNullException("scrollViewer"); } m_scrollViewer = scrollViewer; m_scrollViewer.ScrollToEnd(); m_scrollViewer.ScrollChanged += ScrollChanged; } private void ScrollChanged(object sender, ScrollChangedEventArgs e) { // User scroll event : set or unset autoscroll mode if (e.ExtentHeightChange == 0) { m_doScroll = m_scrollViewer.VerticalOffset == m_scrollViewer.ScrollableHeight; } // Content scroll event : autoscroll eventually if (m_doScroll && e.ExtentHeightChange != 0) { m_scrollViewer.ScrollToVerticalOffset(m_scrollViewer.ExtentHeight); } } public void Dispose() { m_scrollViewer.ScrollChanged -= ScrollChanged; } 

然后只需在XAML中使用它:

    

使用local作为名称空间导入位于相关XAML文件的顶部。 这避免了在其他答案中看到的static bool

 bool autoScroll = false; if (e.ExtentHeightChange != 0) { if (infoScroll.VerticalOffset == infoScroll.ScrollableHeight - e.ExtentHeightChange) { autoScroll = true; } else { autoScroll = false; } } if (autoScroll) { infoScroll.ScrollToVerticalOffset(infoScroll.ExtentHeight); } 

воттаквроде-быпривельнеечемуWallstreetProgrammer

使用TextBox的“TextChanged”事件和ScrollToEnd()方法怎么样?

  private void consolebox_TextChanged(object sender, TextChangedEventArgs e) { this.consolebox.ScrollToEnd(); } 

在Windows 10中,.ScrollToVerticalOffset已过时。 所以我像这样使用ChangeView

 TextBlock messageBar; ScrollViewer messageScroller; private void displayMessage(string message) { messageBar.Text += message + "\n"; double pos = this.messageScroller.ExtentHeight; messageScroller.ChangeView(null, pos, null); } 

重写以前的答案以使用浮点比较。 请注意,此解决方案虽然简单,但只要内容滚动到底部,就会阻止用户滚动。

 private bool _should_auto_scroll = true; private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e) { if (Math.Abs(e.ExtentHeightChange) < float.MinValue) { _should_auto_scroll = Math.Abs(ScrollViewer.VerticalOffset - ScrollViewer.ScrollableHeight) < float.MinValue; } if (_should_auto_scroll && Math.Abs(e.ExtentHeightChange) > float.MinValue) { ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight); } }