WPF在UI线程上追加文本块,但WinForms却不会?

5
我最近将我的应用程序从WinForms转换到WPF,并且我对大多数新功能感到满意。然而,我遇到了一个主要的障碍。当我不断将文本附加到我的文本框中时,UI线程会被阻塞得如此厉害,以至于我除了观看它附加文本之外什么都做不了!我需要能够在我的选项卡控件中切换选项卡、单击按钮等等。奇怪的是,在WinForms中,我绝对没有UI线程的减速!
因此,这里是关于我的应用程序的一些背景:它作为“操作队列”的一部分运行其他进程,并将这些进程的标准输出和标准错误输出到两个独立的文本框以及日志文本框中(这些是受影响的文本框)。在低输出进程上,没有减速,但是当我使用诸如SVN检出和文件复制之类的进程时,我会一次获得很多文本输出,所有它能做的就是附加文本。
以下是我的打印代码:

public void PrintOutput(String s)
{
    String text = s + Environment.NewLine;
    Window.Dispatcher.Invoke(new StringArgDelegate(Window.PrintOutput), text);
    Debug.Log("d " + text);
}

public void PrintLog(String s)
{
    ClearLogButtonEnabled = true;
    String text = s + Environment.NewLine;
    Window.Dispatcher.Invoke(new StringArgDelegate(Window.PrintLog), text);
}

以及相应的代码后端:


public void PrintOutput(String s)
{
     outputTextBox.AppendText(s);
     outputTextBox.ScrollToEnd();
     if (!clearOutputButton.IsEnabled) clearOutputButton.IsEnabled = true;
}

public void PrintLog(String s)
{
     logTextBox.AppendText(s);
     logTextBox.ScrollToEnd();
}

为了避免被指责我也在UI线程上工作,这里是启动独立的工作线程的代码:


Thread actionThread = new Thread(new ThreadStart(ActionManager.Instance().ExecuteActions));
actionThread.Name = "Action Manager Work Thread";
actionThread.Start();

这是处理启动、运行和清理所有辅助进程的线程。这些进程使用上面显示的打印方法来打印它们的stdout/stderr输出。此外,每个进程都有自己的线程!


Thread procThread = new Thread(new ThreadStart(StartProcess));
procThread.Name = action.Name + "_" + Guid.NewGuid();
procThread.Start();

我的担忧是WPF在某些方面的Invokes速度较慢,我很困扰。我花了很多时间将这个应用程序从WinForms转换到WPF,所以如果有人知道为什么打印速度会如此缓慢,请告诉我!
编辑:
我还应该补充一点,我正在使用RichTextBox而不是TextBox,并且需要RichTextBox的功能来加粗某些文本。如果有一种更不繁琐的TextBox类可以提供粗体文本,请告诉我。

也可能是https://dev59.com/4XE95IYBdhLWcg3wE52C的重复问题。 - 3dd
@3dd 我看了第二个链接,它很有帮助。然而,对我来说很重要的是,在不影响UI线程的情况下获得实时更新的进程输出(这在WinForms中是可能的)。链接的答案和我的后续测试让我相信,WPF在这方面比WinForms慢得多,这非常不幸。我可能需要考虑制作一个性能更好的TextBox类... - Darkhydro
是的,阅读有关文本框性能的信息也让我相信实现自定义文本框可能是唯一的选择。 - 3dd
请查看我的示例,了解如何使用WPF正确地完成此操作。WPF在任何方面都不会“更慢”,问题在于您需要忘记WinForms的思维方式,并学习正确使用WPF。 - Federico Berasategui
1
@HighCore 我同意WPF比WinForms更强大,但非常烦人的是,我不能直接使用RichTextBox,因为它比WinForms的RichTextBox慢得多。相反,在WPF中让它正常工作,我必须做无尽的变通和“聪明的技巧”,以获得普通文本框的功能。这真的是一个微不足道的任务;我只想打印文本,并且其中一些应该加粗。你会认为这很容易... - Darkhydro
显示剩余7条评论
1个回答

6

WPF的RichTextBox是一个非常重量级的UI元素,因为它不仅允许富文本内容以WPF Document的形式存在,还具有编辑功能。

在这种情况下,您真正需要的是FlowDocumentScrollViewer

这是我日志查看器示例的一个小改编,它使用FlowDocumentScrollViewer代替ItemsControl。其优点是该UI元素允许选择和复制文本,同时保留了您需要的富文本功能:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <FlowDocumentScrollViewer Document="{Binding}"/>
</Window>

代码后台:

public partial class MainWindow : Window
    {
        private System.Random random;
        private string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
        private List<string> words;
        private int maxword;
        private int index;

        private FlowDocument doc;
        private Paragraph paragraph;

        public MainWindow()
        {
            InitializeComponent();

            DataContext = doc = new FlowDocument();

            doc.Blocks.Add(paragraph = new Paragraph());

            Task.Factory.StartNew(AddDataLoop);
        }

        private void AddDataLoop()
        {
            random = new Random();
            words = TestData.Split(' ').ToList();
            maxword = words.Count - 1;

            while (true)
            {
                Thread.Sleep(10);
                Dispatcher.BeginInvoke((Action) (AddRandomEntry));
            }
        }

        private void AddRandomEntry()
        {
            var run = new Run(string.Join(" ", Enumerable.Range(5, random.Next(10, 50))
                                                         .Select(x => words[random.Next(0, maxword)])));

            var isBold = random.Next(1, 10) > 5;

            if (isBold)
                paragraph.Inlines.Add(new Bold(run));
            else
                paragraph.Inlines.Add(run);

            paragraph.Inlines.Add(new LineBreak());
        }
    }

结果:

enter image description here

  • 再次证明,在winforms中无法实现的功能,WPF都可以做到;相反的情况则不成立。这使得winforms成为一种实际上被更新、更有能力的技术所取代的过时技术。

  • 请注意,我在每个新条目之间设置了10毫秒的延迟。这几乎是实时的,UI没有显示任何减速或闪烁或任何质量下降。

  • 与我的其他示例一样,请注意大部分的后台代码实际上是生成随机文本的模板,唯一相关的代码行是paragraph.Inlines.Add(...)

  • WPF非常出色。- 只需将我的代码复制并粘贴到文件 -> 新项目 -> WPF应用程序中,就可以亲自查看结果。

  • 如果您需要进一步帮助,请告诉我。


1
这个解决方案效果不错,性能甚至比使用大量TextBlocks的ItemsControl还要好,我可以将需要加粗的文本加粗并复制粘贴。唯一遇到的障碍是让自动滚动起作用。我现在仍在与此问题斗争,如果您有建议,请告诉我。我通过将其放入ScrollViewer中使其自动滚动(因为似乎无法访问FlowDocumentScrollViewer的ScrollViewer,它没有子元素?!),但这会禁用鼠标滚动。我可能最终会做一些事件操作。 - Darkhydro
@Darkhydro,那个方法在哪里/何时被调用?它应该在“Loaded”事件发生后调用,而不是之前。 - Federico Berasategui
我在窗口的Loaded事件中调用它,在最后阶段。 - Darkhydro
我在使用FlowDocumentScrollViewer时遇到了性能问题。如果您想查看,我已经在https://dev59.com/Iuo6XIcBkEYKwwoYIAsf发布了另一个问题。 - Darkhydro
它解决了我的问题,但只有一件事,在文本块中我之前是在0位置插入新行。是否有可能控件自动滚动到最后插入的行? - Mark
显示剩余4条评论

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