我有一个WPF应用程序,其中包含一个多行TextBox,用于显示调试文本输出.
如何设置TextBox以便将文本附加到框中,它会自动滚动到文本框的底部?
Sco*_*son 41
@BojinLi提供的答案效果很好.在阅读了@GazTheDestroyer链接的答案之后,我决定为TextBox实现我自己的版本,因为它看起来更干净.
总而言之,您可以使用附加属性来扩展TextBox控件的行为.(称为ScrollOnTextChanged)
使用它很简单:
<TextBox src:TextBoxBehaviour.ScrollOnTextChanged="True" VerticalScrollBarVisibility="Auto" />
Run Code Online (Sandbox Code Playgroud)
这是TextBoxBehaviour类:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
namespace MyNamespace
{
public class TextBoxBehaviour
{
static readonly Dictionary<TextBox, Capture> _associations = new Dictionary<TextBox, Capture>();
public static bool GetScrollOnTextChanged(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(ScrollOnTextChangedProperty);
}
public static void SetScrollOnTextChanged(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(ScrollOnTextChangedProperty, value);
}
public static readonly DependencyProperty ScrollOnTextChangedProperty =
DependencyProperty.RegisterAttached("ScrollOnTextChanged", typeof (bool), typeof (TextBoxBehaviour), new UIPropertyMetadata(false, OnScrollOnTextChanged));
static void OnScrollOnTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var textBox = dependencyObject as TextBox;
if (textBox == null)
{
return;
}
bool oldValue = (bool) e.OldValue, newValue = (bool) e.NewValue;
if (newValue == oldValue)
{
return;
}
if (newValue)
{
textBox.Loaded += TextBoxLoaded;
textBox.Unloaded += TextBoxUnloaded;
}
else
{
textBox.Loaded -= TextBoxLoaded;
textBox.Unloaded -= TextBoxUnloaded;
if (_associations.ContainsKey(textBox))
{
_associations[textBox].Dispose();
}
}
}
static void TextBoxUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
var textBox = (TextBox) sender;
_associations[textBox].Dispose();
textBox.Unloaded -= TextBoxUnloaded;
}
static void TextBoxLoaded(object sender, RoutedEventArgs routedEventArgs)
{
var textBox = (TextBox) sender;
textBox.Loaded -= TextBoxLoaded;
_associations[textBox] = new Capture(textBox);
}
class Capture : IDisposable
{
private TextBox TextBox { get; set; }
public Capture(TextBox textBox)
{
TextBox = textBox;
TextBox.TextChanged += OnTextBoxOnTextChanged;
}
private void OnTextBoxOnTextChanged(object sender, TextChangedEventArgs args)
{
TextBox.ScrollToEnd();
}
public void Dispose()
{
TextBox.TextChanged -= OnTextBoxOnTextChanged;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
这个解决方案的灵感来自Scott Ferguson的附加属性解决方案,但避免存储关联的内部字典,因此代码更短:
using System;
using System.Windows;
using System.Windows.Controls;
namespace AttachedPropertyTest
{
public static class TextBoxUtilities
{
public static readonly DependencyProperty AlwaysScrollToEndProperty = DependencyProperty.RegisterAttached("AlwaysScrollToEnd",
typeof(bool),
typeof(TextBoxUtilities),
new PropertyMetadata(false, AlwaysScrollToEndChanged));
private static void AlwaysScrollToEndChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb != null) {
bool alwaysScrollToEnd = (e.NewValue != null) && (bool)e.NewValue;
if (alwaysScrollToEnd) {
tb.ScrollToEnd();
tb.TextChanged += TextChanged;
} else {
tb.TextChanged -= TextChanged;
}
} else {
throw new InvalidOperationException("The attached AlwaysScrollToEnd property can only be applied to TextBox instances.");
}
}
public static bool GetAlwaysScrollToEnd(TextBox textBox)
{
if (textBox == null) {
throw new ArgumentNullException("textBox");
}
return (bool)textBox.GetValue(AlwaysScrollToEndProperty);
}
public static void SetAlwaysScrollToEnd(TextBox textBox, bool alwaysScrollToEnd)
{
if (textBox == null) {
throw new ArgumentNullException("textBox");
}
textBox.SetValue(AlwaysScrollToEndProperty, alwaysScrollToEnd);
}
private static void TextChanged(object sender, TextChangedEventArgs e)
{
((TextBox)sender).ScrollToEnd();
}
}
}
Run Code Online (Sandbox Code Playgroud)
据我所知,它的行为完全符合要求.这是一个测试用例,在窗口中有几个文本框,允许AlwaysScrollToEnd以各种方式设置附加属性(硬编码,CheckBox.IsChecked绑定和代码隐藏):
XAML:
<Window x:Class="AttachedPropertyTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AttachedPropertyTest" Height="800" Width="300"
xmlns:local="clr-namespace:AttachedPropertyTest">
<Window.Resources>
<Style x:Key="MultiLineTB" TargetType="TextBox">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Height" Value="60"/>
<Setter Property="Text" Value="{Binding Text, ElementName=tbMaster}"/>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Background="LightYellow" Name="tbMaster" Height="150" AcceptsReturn="True"/>
<TextBox Style="{StaticResource MultiLineTB}" Grid.Row="1" local:TextBoxUtilities.AlwaysScrollToEnd="True"/>
<TextBox Style="{StaticResource MultiLineTB}" Grid.Row="2"/>
<TextBox Style="{StaticResource MultiLineTB}" Grid.Row="3" Name="tb3" local:TextBoxUtilities.AlwaysScrollToEnd="True"/>
<TextBox Style="{StaticResource MultiLineTB}" Grid.Row="4" Name="tb4"/>
<CheckBox Grid.Column="1" Grid.Row="4" IsChecked="{Binding (local:TextBoxUtilities.AlwaysScrollToEnd), Mode=TwoWay, ElementName=tb4}"/>
<Button Grid.Row="5" Click="Button_Click"/>
</Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)
代码隐藏:
using System;
using System.Windows;
using System.Windows.Controls;
namespace AttachedPropertyTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
void Button_Click(object sender, RoutedEventArgs e)
{
TextBoxUtilities.SetAlwaysScrollToEnd(tb3, true);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
17115 次 |
| 最近记录: |