Ela*_*lad 31 c# wpf wpf-controls .net-3.5
我想在ScrollViewer包装中创建以下行为ContentControl:
当ContentControl高度增长时,ScrollViewer应自动滚动到结尾.这很容易通过使用来实现ScrollViewer.ScrollToEnd().
但是,如果用户使用滚动条,则不应再进行自动滚动.这类似于VS输出窗口中发生的情况.
问题是要知道由于用户滚动而发生滚动的时间以及由于内容大小发生变化而发生滚动的时间.我试图用玩ScrollChangedEventArgs的ScrollChangedEvent,但不能让它开始工作.
理想情况下,我不想处理所有可能的鼠标和键盘事件.
小智 59
您可以使用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);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 27
以下是几个来源的改编.
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); }
}
}
Run Code Online (Sandbox Code Playgroud)
像这样在XAML中使用它:
<ScrollViewer Height="230" HorizontalScrollBarVisibility="Auto" extensionProperties:ScrollViewerExtension.AlwaysScrollToEnd="True">
<TextBlock x:Name="Trace"/>
</ScrollViewer>
Run Code Online (Sandbox Code Playgroud)
如果内容先前已向下滚动,则此代码将自动滚动到内容增长时结束.
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>
Run Code Online (Sandbox Code Playgroud)
代码背后:
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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是我用过的一个方法,效果很好。基于两个依赖属性。它避免了代码隐藏和计时器,如其他答案所示。
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));
}
Run Code Online (Sandbox Code Playgroud)
这使用定义为的处理程序。
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;
}
Run Code Online (Sandbox Code Playgroud)
然后只需在 XAML 中使用它即可:
<ScrollViewer VerticalScrollBarVisibility="Auto"
local:ScrollViewerEx.AutoScrollToEnd="True">
<TextBlock x:Name="Test test test"/>
</ScrollViewer>
Run Code Online (Sandbox Code Playgroud)
作为local相关 XAML 文件顶部的命名空间导入。这避免了static bool在其他答案中看到的情况。