1

我有一个用于显示日志消息的 LogTextBox 类:

public class LogTextBox : TextBox
{
    int maxMessageCount, messageCount;

    //number of characters for each message
    List<int> messageLengths;

    public LogTextBox(int maxMessageCount)
    {
        this.messageCount = 0;
        this.maxMessageCount = maxMessageCount;
        this.messageLengths = new List<int>();

        IsReadOnly = true;
        IsUndoEnabled = false;
    }

    public void Log(string message)
    {
        if (messageCount >= maxMessageCount)
        {
            Dispatcher.Invoke((Action)delegate()
            {
                //statement 1
                string text = Text.Remove(0, messageLengths[0]);

                //statement 2
                Text = text + message + '\n';

                //statement 3
                ScrollToEnd();
            });

            messageLengths.RemoveAt(0);
            messageLengths.Add(message.Length + 1);
        }
        else
        {
            Dispatcher.Invoke((Action)delegate()
            {
                AppendText(message + '\n');
                ScrollToEnd();
            });

            messageLengths.Add(message.Length + 1);
            messageCount++;
        }
    }
}

public class Test
{
    public LogTextBox logView;

    public Timer timer;

    [STAThread]
    public static void Main()
    {
        Application app = new Application();
        Test test = new Test();

        test.logView = new LogTextBox(200);

        test.timer = new Timer(200);
        test.timer.Elapsed += new ElapsedEventHandler(test.timer_Elapsed);
        test.timer.Start();

        app.Run(main);
    }

    int line = 0;
    void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        logView.Log(GetMessage(line++));
    }

    private string GetMessage(int line)
    {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 1000; i++)
            builder.Append(line + " ");

        builder.Append('\n');

        return builder.ToString();
    }
}

在上述配置中,语句 3 执行 10 次平均需要 200 毫秒。如果将语句 2 注释掉,则语句 3 执行 10 次平均需要 0.1 毫秒。在这两种情况下,Log 方法的其他部分平均需要 10 毫秒。语句 1 和语句 2 的执行时间很小,并不重要。我使用高分辨率秒表进行测量。

为什么更新 Text 属性时 ScrollToEnd 需要这么长时间?ScrollToEnd(语句 3)的执行时间与 Text 属性的大小成正比,因为如果在 LogTextBox 构造函数中将 maxMessageCount 设置为 500,则需要 500 毫秒。我必须通过删除第一条消息来更新文本以限制使用的内存,但我还没有找到任何其他方式。有没有其他方法可以删除第一条消息?

编辑:

我按照建议尝试了 AvalonEdit,并从 TextEditor 而不是 TextBox 派生。我不必更改代码,因为方法名称相同。ScrollToEnd(Statement 3) 在相同的测试配置下平均花费 0.02 毫秒,并且无论 Text 属性的大小如何,它都保持不变。结果,我的性能问题得到了解决,我将使用 AvalonEdit。我给了 Jacob 赏金,因为他首先建议了 AvalonEdit。

TextBoxBase.ScrolltoEnd 调用 UpdateLayout(如下所示,使用 Reflector),我猜这就是它性能不佳的原因,而 AvalonEdit 的 TextEditor.ScrollToEnd 只调用 ScrollViewer.ScrollToEnd。

public void ScrollToEnd()
{
    if (this.ScrollViewer != null)
    {
        base.UpdateLayout();
        this.ScrollViewer.ScrollToEnd();
    }
}  
4

4 回答 4

0

您是否尝试过将语句 2 从

       //statement 2
       Text = text + message + '\n';

AppendText(message);
AppendText('\n');

?

这只是一个猜测,但由于所写的语句 2 每次都分配一个新的字符串对象,垃圾收集器可能会开始释放旧的字符串。我相信 AppendText 的实现方式可以避免中间分配。

于 2013-03-18T21:49:24.493 回答
0

为什么不考虑使用不同的文本框控件?

你可以试试支持文本高亮的AvalonEdit,你可以在这里阅读http://www.codeproject.com/KB/edit/AvalonEdit.aspx

似乎比原生 WPF 富文本控件更快。

于 2013-03-19T08:40:10.353 回答
0

如果易于阅读是主要的文档消费场景。然后你可以尝试使用Flow Document

注意:此 FlowDocument 旨在优化查看和可读性。RichText 框 表示对 FlowDocument 对象进行操作的富编辑控件。

编辑:

  1. 如果您不希望应用程序中有超过一万个搜索结果,那么到目前为止,一个 TextBlock 控件或只读多行 TextBox 就足够了。TextBox 类有一个 AppendText() 方法,该方法对您来说应该足够快。您不必删除第一条消息。

  2. 您可以考虑使用不同的文本框控件。这是一个完全从头开始的 SharpDevelop 的 Wpf 文本编辑器。它叫AvalonEdit,codeproject上有一篇好文章:http: //www.codeproject.com/KB/edit/AvalonEdit.aspx 看来他对大内容做了优化。

于 2013-03-15T04:12:03.030 回答
0

我发现logbox.ScrollToVerticalOffset(double.MaxValue);它比logbox.ScrollToEnd();. 也许是因为它避免了对UpdateLayout().

于 2017-10-26T17:27:52.590 回答