自动垂直滚动的多行文本框

47

在互联网上,包括Stack Overflow等网站,有很多类似的问题,但是提出的解决方案在我的情况下并不适用。 场景:在XAML中有一个日志文本框。

 <TextBox Name="Status"
          Margin="5"
          Grid.Column="1"
          Grid.Row="5"
          HorizontalAlignment="Left"
          VerticalAlignment="Top"
          Width="600"
          Height="310"/>

在代码后端有一些方法,它们执行某些操作并将一些多行消息(也许这就是问题所在?)添加到该文本框中:

private static void DoSomeThings(TextBox textBox)
{
   // do work
   textBox.AppendText("Work finished\r\n"); // better way than Text += according to msdn
   // do more
   textBox.AppendText("One more message\r\n");
   ...
}

private static void DoSomething2(TextBox textBox)
{
   // same as first method
}

执行所有操作后需要滚动到文本框底部。尝试了ScrollToEnd(),ScrollToLine,将文本框包装到ScrollViewer中,选择和插入符号变通方法,将ScrollToEnd附加到TextChanged事件。这些都不起作用,执行后超出文本框高度的行仍需要手动滚动。抱歉重复问题,我想我错过了一些可以快速解决的小问题,需要有新视角看待问题。


当你说你需要“滚动到文本框的底部”时,你真正意思是“滚动”,就像“最后添加的文本完全可见”吗?还是你想让插入符号位于文本框的最末端? - Daniel Hilgarth
首先,为了说明问题,我截取了一张屏幕截图 - 左边是我得到的结果,右边是我需要的结果(手动向下滚动):http://i.piccy.info/i7/0c105234c75b7031df050587f72771b4/1-5-3848/56682026/120120114019_6.jpg - Jaded
4个回答

88

根据这个问题:当TextBox在非活动选项卡中时,TextBox.ScrollToEnd不起作用

您需要聚焦文本框,更新插入符位置,然后滚动到末尾:

Status.Focus();
Status.CaretIndex = Status.Text.Length;
Status.ScrollToEnd();

编辑

文本框示例:

<TextBox TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" 
         AcceptsReturn="True" Name="textBox"/>

尝试过了。插入符确实在文本框中的最后一行,但是滚动到它并没有发生,应用程序运行后仍然可以看到顶部行(请参见上面评论中的左侧部分图像)。 - Jaded
1
我已经添加了一个文本框的示例,滚动条可以正常工作。你可能需要添加TextWrapping属性。 - Adrian Fâciu
看起来问题在于文本框在应用程序加载后(在页面构造函数中)执行操作时没有机会刷新/使自己无效。当我将逻辑放在按钮处理程序中并手动执行它时,滚动确实像魅力一样工作。 编辑在页面构造函数中添加Loaded +=(sender,args)=> TestOperations()确实奏效。感谢您的帮助。我想知道的唯一一件事是,我可以手动执行哪个方法以达到相同的效果,同时仍然将代码留在页面构造函数中。 - Jaded
你是在调用 InitializeComponents 之前还是之后执行代码? - Daniel Hilgarth
我已经在InitializeComponents方法后的页面构造器中添加了代码,对我来说仍然可以正常工作。 - Adrian Fâciu
不,代码是在InitializeComponents之后的,像这样:InitializeComponent(); ShowsNavigationUI = false; const bool debugMode = true; if (debugMode) { TestOperations(); } // 应用需求 - Jaded

19

如果将其制作成一个简单的自定义控件,则无需编写任何代码即可进行滚动。

public class ScrollingTextBox : TextBox {

    protected override void OnInitialized (EventArgs e) {
        base.OnInitialized(e);
        VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
        HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
    }

    protected override void OnTextChanged (TextChangedEventArgs e) {
        base.OnTextChanged(e);
        CaretIndex = Text.Length;
        ScrollToEnd();
    }

}
如果您正在使用WPF,最好使用绑定而不是将文本框传递到代码后面。

2
比其他关于同一主题的帖子要简单得多。对于我的简单应用程序,我甚至只保留了OnTextChanged(...){base.OnTextChanged(e);ScrollToEnd();},因为它已经足够了。 - NGI
已经接受的答案可行,但如果您遵循MVVM模式,这是实现它的方法。 - Trevor Vance
不需要创建自定义控件,只需将 ((TextBox)sender).CaretIndex = ((TextBox)sender).Text.Length; ((TextBox)sender).ScrollToEnd(); 添加到 TextBox 的 TextChanged 事件处理程序中即可。 - Sinan Ceylan

12
如果你不太喜欢编写后端代码,这里有一个附加属性可以帮你解决问题:
namespace YourProject.YourAttachedProperties
{

    public class TextBoxAttachedProperties
    {

        public static bool GetAutoScrollToEnd(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToEndProperty);
        }

        public static void SetAutoScrollToEnd(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToEndProperty, value);
        }

        // Using a DependencyProperty as the backing store for AutoScrollToEnd.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AutoScrollToEndProperty =
        DependencyProperty.RegisterAttached("AutoScrollToEnd", typeof(bool), typeof(TextBoxAttachedProperties), new PropertyMetadata(false, AutoScrollToEndPropertyChanged));

        private static void AutoScrollToEndPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if(d is TextBox textbox && e.NewValue is bool mustAutoScroll && mustAutoScroll)
            {
                textbox.TextChanged += (s, ee)=> AutoScrollToEnd(s, ee, textbox);
            }
        }

        private static void AutoScrollToEnd(object sender, TextChangedEventArgs e, TextBox textbox)
        {
            textbox.ScrollToEnd();
        }
    }
}

然后在你的 XAML 中只需这样做:

<TextBox
    AcceptsReturn="True"
    myAttachedProperties:TextBoxAttachedProperties.AutoScrollToEnd="True"/>

只需要不要忘记在你的XAML文件顶部添加

xmlns:myAttachedProperties="clr-namespace:YourProject.YourAttachedProperties"

大功告成


1
很棒的解决方案。这个问题的最佳答案。谢谢! - Frank im Wald
一点不错。像魔法般地运作。 - ardmark

2
感谢!我已经添加了这个功能来记住原始焦点:
var oldFocusedElement = FocusManager.GetFocusedElement(this);

this.textBox.Focus();
this.textBox.CaretIndex = this.textBox.Text.Length;
this.textBox.ScrollToEnd();

FocusManager.SetFocusedElement(this, oldFocusedElement);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接