C# 如何自动滚动ScrollViewer-仅当用户未更改滚动位置时
我想在包装C# 如何自动滚动ScrollViewer-仅当用户未更改滚动位置时,c#,wpf,.net-3.5,wpf-controls,C#,Wpf,.net 3.5,Wpf Controls,我想在包装ContentControl的ScrollViewer中创建以下行为: 当ContentControl高度增加时,ScrollViewer应自动滚动到末尾。使用ScrollViewer.ScrollToEnd()可以轻松实现这一点。 但是,如果用户使用滚动条,则自动滚动不应再发生。例如,这类似于VS输出窗口中发生的情况 问题是要知道何时由于用户滚动而发生滚动,何时由于内容大小更改而发生滚动。我试图玩ScrollChangedEvent的ScrollChangedEventArgs,但无
ContentControl
的ScrollViewer
中创建以下行为:当
ContentControl
高度增加时,ScrollViewer
应自动滚动到末尾。使用ScrollViewer.ScrollToEnd()
可以轻松实现这一点。
但是,如果用户使用滚动条,则自动滚动不应再发生。例如,这类似于VS输出窗口中发生的情况 问题是要知道何时由于用户滚动而发生滚动,何时由于内容大小更改而发生滚动。我试图玩
ScrollChangedEvent
的ScrollChangedEventArgs
,但无法让它工作
理想情况下,我不想处理所有可能的鼠标和键盘事件。如果以前一直向下滚动,当内容增长时,此代码将自动滚动到结尾 XAML:
您可以使用ScrollChangedEventArgs.ExtentTheightChange了解ScrollChange是否是由于内容更改或用户操作引起的。。。 当内容不变时,滚动条位置设置或取消设置自动滚动模式。 内容更改后,可以应用自动滚动 代码隐藏:
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();
}
}
}
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中使用它,如下所示:
<ScrollViewer Height="230" HorizontalScrollBarVisibility="Auto" extensionProperties:ScrollViewerExtension.AlwaysScrollToEnd="True">
<TextBlock x:Name="Trace"/>
</ScrollViewer>
我使用了一种方法,效果很好。基于两个依赖属性。它避免了代码隐藏和计时器,如另一个答案所示
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文件的顶部。这避免了在其他答案中看到的静态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);
}
华尔街程序员使用文本框的“TextChanged”事件和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);
}
}
private bool\u应该\u auto\u scroll=true;
私有void ScrollViewer_OnScrollChanged(对象发送方,ScrollChangedEventArgs e){
if(Math.Abs(e.extenstheightchange)float.MinValue){
ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight);
}
}
在Windows builds 17763及更高版本上,可以在ScrollViewer上进行设置,仅此而已
然而:还有一个漏洞仍然存在:根据第二个答案,为什么不能是:
private void ScrollViewer\u ScrollChanged(对象发送者,ScrollChangedEventArgs e)
{
如果(例如,扩展灯光更改!=0)
{
ScrollViewer.ScrollToVerticalOffset(ScrollViewer.ExtentHeight);
}
}
我已经在我的应用程序上进行了测试,它可以正常工作。我希望使用文本框进行此操作,结果证明,使用此代码并将文本框嵌入ScrollViewer比尝试使用文本框的内置滚动更容易。谢谢,我发现让ScrollViewer根据文本块的内容自动滚动非常有用。我做了一些小的修改,比如使用private bool AutoScroll=true
并将其放入方法中private Boolean AutoScroll=true
导致“无效表达式术语‘private’”错误。问题,这是“有效的WPF风格”吗?或者说,使用绑定是否会破坏WPF的“精神”呢?我曾试图提出一个更简单的解决方案,但结果与此非常相似。尽管如此,我还是将AutoScroll变量放在了hander内部而不是外部,看到了吧,我的朋友,你是个英雄!工作完美。滚动到底部时自动滚动(从初始设置或用户恢复时)。当用户滚动位置不是底部时保持固定。很好的信息聚合+1还用于附加属性,这些属性可以添加到我的工具包中,并减少重复的代码隐藏。这非常好。拥有可以干净地工作的附加属性总是好的。这个答案有一个错误。\u autoScroll
字段是静态的,这意味着如果该类被多次使用,则该状态将交叉使用。该状态需要明确绑定到ScrollViewer
。此外,ReSharper报告浮点类型之间的相等性比较,这是一个禁忌。它非常适合我的需要。谢谢你!可与ListView一起使用?是否有任何方法将其附加到ListView的ScrollViewer?如果有要滚动的内容,此代码将全天每2秒检查一次。这比下面的事件驱动解决方案更慢、效率更低。只能在本网站上使用英语。您需要修复代码(缩进)。
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);
}
}