Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF append text blocks UI thread heavily but WinForms doesn't?

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.

like image 794
Darkhydro Avatar asked Jul 09 '14 21:07

Darkhydro


1 Answers

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.

like image 87
Federico Berasategui Avatar answered Nov 15 '22 04:11

Federico Berasategui