如何自动滚动 ScrollViewer - 仅当用户未更改滚动位置时
Posted
技术标签:
【中文标题】如何自动滚动 ScrollViewer - 仅当用户未更改滚动位置时【英文标题】:How to automatically scroll ScrollViewer - only if the user did not change scroll position 【发布时间】:2011-02-28 09:55:03 【问题描述】:我想在包含ContentControl
的ScrollViewer
中创建以下行为:
当 ContentControl
高度增长时,ScrollViewer
应该会自动滚动到末尾。这很容易通过使用ScrollViewer.ScrollToEnd()
来实现。
但是,如果用户使用滚动条,则不应再发生自动滚动。这类似于 VS 输出窗口中发生的情况。
问题是要知道什么时候因为用户滚动而发生了滚动,什么时候因为内容大小发生了变化。我尝试使用ScrollChangedEventArgs
和ScrollChangedEvent
,但无法正常工作。
理想情况下,我不想处理所有可能的鼠标和键盘事件。
【问题讨论】:
【参考方案1】:如果内容之前一直向下滚动,则此代码将在内容增长时自动滚动到结束。
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>
后面的代码:
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();
【讨论】:
此代码将全天每 2 秒检查一次是否有要滚动的内容。与下面的事件驱动解决方案相比,这既慢又效率低。【参考方案2】:您可以使用 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);
【讨论】:
我希望这种行为与 TextBox 一起使用,结果证明使用此代码并将 TextBox 嵌入到 ScrollViewer 中而不是尝试使用 TextBox 的内置滚动是最简单的。 谢谢,我发现让 ScrollViewer 根据 TextBlock 的内容自动滚动非常有用。我确实做了一些小的修改,比如使用private bool AutoScroll = true
并将其放入方法中。 private Boolean AutoScroll = true
导致“无效的表达式术语 'private'”错误。问题,这是“有效的 WPF 样式”吗?还是不使用绑定破坏了 WPF 的“精神”?
我试图做一个更简单的解决方案,但最终很像这个。不过,我将 AutoScroll 变量放在处理程序中而不是外部,请参阅***.com/questions/25761795/…
你,我的朋友,是个英雄!【参考方案3】:
这里有几个来源的改编。
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 中使用它:
<ScrollViewer Height="230" HorizontalScrollBarVisibility="Auto" extensionProperties:ScrollViewerExtension.AlwaysScrollToEnd="True">
<TextBlock x:Name="Trace"/>
</ScrollViewer>
【讨论】:
完美运行。滚动到底部时自动滚动(从初始设置或用户恢复时)。当用户滚动位置不是底部时保持固定。很好的信息汇总。 +1 也适用于可以添加到我的工具包并减少重复代码隐藏的附加属性。 这太棒了。拥有干净利落的附加属性总是好的。 这个答案有误。_autoScroll
字段是静态的,这意味着如果多次使用此类,则状态将交叉使用。该状态需要明确绑定到ScrollViewer
。此外,ReSharper 报告浮点类型之间的相等比较,这是一个禁忌。
完美满足我的需求。谢谢!
可以和 ListView 一起使用吗?有什么办法可以将它附加到 ListView 的 ScrollViewer 上?【参考方案4】:
这是我使用的一种方法,效果很好。基于两个依赖属性。它避免了代码落后和计时器,如另一个答案所示。
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 中简单地使用它:
<ScrollViewer VerticalScrollBarVisibility="Auto"
local:ScrollViewerEx.AutoScrollToEnd="True">
<TextBlock x:Name="Test test test"/>
</ScrollViewer>
local
是相关 XAML 文件顶部的命名空间导入。这避免了在其他答案中看到的static bool
。
【讨论】:
【参考方案5】: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);
Вот так вроде-бы привельнее чему 华尔街程序员
【讨论】:
В английском языке на этом сайте / 本网站仅提供英文版。你需要修复你的代码(缩进)。【参考方案6】:使用TextBox的“TextChanged”事件和ScrollToEnd()方法怎么样?
private void consolebox_TextChanged(object sender, TextChangedEventArgs e)
this.consolebox.ScrollToEnd();
【讨论】:
【参考方案7】:在 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);
【讨论】:
【参考方案8】:重写以前的答案以使用浮点比较。请注意,此解决方案虽然简单,但会在内容滚动到底部时阻止用户滚动。
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);
【讨论】:
【参考方案9】:在 Windows 17763 及更高版本上,可以在ScrollViewer
上设置VerticalAnchorRatio="1"
,仅此而已。
但是:仍有一个错误:https://github.com/Microsoft/microsoft-ui-xaml/issues/562
【讨论】:
【参考方案10】:根据第二个答案,为什么不能这样:
private void ScrollViewer_ScrollChanged(Object sender, ScrollChangedEventArgs e)
if (e.ExtentHeightChange != 0)
ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight);
我已经在我的应用程序上对其进行了测试,它可以工作。
【讨论】:
以上是关于如何自动滚动 ScrollViewer - 仅当用户未更改滚动位置时的主要内容,如果未能解决你的问题,请参考以下文章