5

I recently converted my application from WinForms to WPF, and I'm happy with most of the new features. However, there is a major stumbling block I've hit. When I append text constantly to my textboxes, the UI thread becomes so blocked up that I can't do anything but watch it append text! I need to be able to switch tabs in my tab control, click buttons, etc etc. The strange thing is that I have absolutely no slowdown in the UI thread in WinForms!

So, here's a little background on my application: it runs other processes as part of an "action queue", and spits out those process's stdout and stderr into two separate textboxes, as well as the log textbox (these are the impacted textboxes). On low output processes, there is no slowdown, but when I use processes like SVN checkout and file copying, I get so much text output at once that all it can do is append text.

Here's my code for printing:


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);
}

and the matching code-behind:


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();
}

Just so that I don't get a bunch of accusations saying that I'm doing my work on the UI thread as well, here is my code for starting up the separate worker thread:


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

This is the thread that handles starting, running, and cleaning up of all the auxiliary processes. These processes use the print methods shown above to print their stdout/stderr output. Additionally, each process gets its own thread!


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

My worry is that WPF is slower at Invokes in some way and that I'm screwed. I put a lot of work into switching this application to WPF from WinForms, so if anyone knows why I'd be getting such a massive slowdown in print speeds please let me know!

EDIT:

I should also add that I am using a RichTextBox, not a TextBox, and that I need the features of the RichTextBox to bold certain text. If there is a way to provide bold text with a less cumbersome TextBox class please let me know.

Darkhydro
  • 1,992
  • 4
  • 24
  • 43
  • Have you tried using the WPF performance suite http://msdn.microsoft.com/en-us/library/aa969767.aspx – 3dd Jul 09 '14 at 21:19
  • Also possible duplicate of http://stackoverflow.com/questions/2465181/is-this-slow-wpf-textblock-performance-expected – 3dd Jul 09 '14 at 21:22
  • @3dd I read up on the 2nd link, and it was helpful. However, it is important to me that I get real time updates in my process output without impacting the UI thread (which is possible with WinForms). The linked answer and my subsequent testing lead me to believe that WPF is MUCH slower than WinForms in this respect, which is very unfortunate. I might have to look into making a performance TextBox class... – Darkhydro Jul 09 '14 at 21:36
  • Yea reading up on the performance of the textbox also leads me to believe that implementing a custom textbox might be the only option – 3dd Jul 09 '14 at 21:39
  • @3dd I've switched to TextBlock and it's performing much better already, however TextBlock does not allow the user to select text... I'm trying to find ways around this now. – Darkhydro Jul 09 '14 at 22:22
  • **See [My Example](http://stackoverflow.com/a/16745054/643085) of how to do this properly with WPF. WPF is not "slower" in any way, the problem is that you need to forget the winforms mentality and learn to use WPF properly.** – Federico Berasategui Jul 09 '14 at 22:53
  • In fact, certain WPF features such as built-in [UI Virtualization](http://www.youtube.com/watch?v=D3Y6DnFpHCA) render winforms completely useless and make it look like a high school kid's science project rather than a serious development platform. – Federico Berasategui Jul 09 '14 at 22:56
  • @HighCore Your VirtualizingStackPanel example is a good one, and indeed makes speed a non-issue. However, I can't select text very well anymore since it's contained within multiple textboxes! I need to be able to select large sections of text and Ctrl+A as well to get all text. The example you gave was enlightening though, thanks for that. – Darkhydro Jul 10 '14 at 00:36
  • @Darkhydro you could build further upon my code, doing some clever tricks either using the `ScrollViewer`'s events or the `ItemsControl`'s events to load only the visible text in a separate TextBox, or even get rid of the ItemsControl altogether and use this same technique by subscribing to the TextBox's ScrollViewer's events and loading the Text on-demand. Either way, it's much easier in WPF because you have full access to the entire visual tree, as opposed to useless winforms where anything non-default requires a bunch of horrible P/Invoke hacks or the like. – Federico Berasategui Jul 10 '14 at 00:42
  • 1
    @HighCore While I agree that WPF has a lot more power than WinForms, it's extremely annoying that I can't just use a RichTextBox out of the box because it's so much slower than WinForms's RichTextBox. Instead, to get it working in WPF I have to do an endless list of workarounds and "clever tricks" to just get normal textbox functionality. This really is a trivial task; all I want is to print text, and some of it should be bolded. You'd think that would be easy... – Darkhydro Jul 10 '14 at 00:57
  • @DarkHydro do you really need the editing capabilities of the `RichTextBox`? or will a read-only like UI suit your need? (provided it includes the select/copy feature)? – Federico Berasategui Jul 10 '14 at 01:05
  • @HighCore As long as I can both make the text bold when I wish and Copy-Paste, I'm happy. Providing, of course, that text output is real time without slowdowns. – Darkhydro Jul 10 '14 at 01:30

1 Answers1

6

The WPF RichTextBox is a very heavy-weight UI element, because it not only allows rich content in the form of a WPF Document, but it also has editing capabilities.

What you really need in this case is a FlowDocumentScrollViewer.

This is a small adaptation of my Log Viewer Sample, which uses a FlowDocumentScrollViewer instead of an ItemsControl. The advantage is that this UI element allows text selection and copying, while retaining the Rich Text capabilities you need:

<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>

Code Behind:

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());
        }
    }

Result:

enter image description here

  • Once again, this proves that there's absolutely nothing you can achieve in winforms that can't be achieved with WPF, whereas the opposite can't be said, obviously. This makes winforms a practically obsolete technology that is literally replaced by newer, more capable ones.

  • Notice that I placed a 10 millisecond delay between each new entry. This is practically real time, and the UI does not show any slowing down or flickering or any quality degradation of any kind.

  • As with my other example, notice that most of the code behind is literally boilerplate to generate random text, the only relevant lines of code are paragraph.Inlines.Add(...).

  • WPF Rocks. - simply copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

  • Let me know if you need further help.

Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • 1
    This solution worked quite well. The performance is even better than using an ItemsControl with a bunch of TextBlocks, and I can bold the text that I need to as well as copy-paste. The only stumbling block I encountered was getting auto-scrolling to work. I'm still fighting that issue now so if you have recommendations let me know. I have it auto-scrolling by surrounding it with a ScrollViewer (since I can't seem to access the FlowDocumentScrollViewer's ScrollViewer, it has 0 children?!), but this disables mouse scrolling. I might end up doing some event shenanigans. – Darkhydro Jul 10 '14 at 18:51
  • I solved this by handling the event in the child and raising it manually in the parent. A little annoying that I can't seem to access the FlowDocumentScrollViewer's ScrollViewer, but this works. – Darkhydro Jul 10 '14 at 19:01
  • @Darkhydro see [this answer](http://stackoverflow.com/a/10279201/643085) which uses `VisualTreeHelper` to get the visual children of any given UI element. also see [MSDN](http://msdn.microsoft.com/en-us/library/ms753391(v=vs.110).aspx) to understand how Visual Trees and Logical Trees work in WPF. – Federico Berasategui Jul 10 '14 at 19:05
  • I looked at several similar answers to the one you linked, and none worked. This is because VisualTreeHelper.GetChildrenCount returned 0 when used on my FlowDocumentScrollViewer. – Darkhydro Jul 10 '14 at 20:32
  • @Darkhydro where/when is that method being called? it should be called after the `Loaded` event has occurred, not before. – Federico Berasategui Jul 10 '14 at 20:33
  • I am calling it in my Window's Loaded event, at the tail end. – Darkhydro Jul 10 '14 at 20:54
  • @Darkhydro I tried `var sv = viewer.GetChildOfType();` with the code from the linked answer and it works for me in the `Loaded` event handler of the Window. Post a separate question about that if you wish. – Federico Berasategui Jul 10 '14 at 20:57
  • I've encountered performance issues with the FlowDocumentScrollViewer. If you'd like to take a look, I've posted another question at http://stackoverflow.com/questions/25044107/how-do-i-increase-performance-of-flowdocumentscrollviewer. – Darkhydro Jul 30 '14 at 18:20
  • It solved my problem, but only one thing, before I was inserting new lines in a textblock at place 0. Is there a possibility that the control auto scrolls to the last inserted line? – Mark Dec 12 '19 at 14:47