[.net] [C#] RichTextBox Speed Issue

Started by
3 comments, last by BradSnobar 15 years, 4 months ago
I'm trying to dump a large amount of text into a RichTextBox using the += operator on the Text property. I'm talking like thousands of lines of text (short lines). The problem is, it takes several minutes to '+=' all the text (it's in a for loop). During this time the program is frozen. When it's all finished a few minutes later, it shows up just fine and I can scroll through all of it, but I'm wondering if there is a way to speed that process up. Is the RichTextBox trying to repaint itself when I add text, or what might be going on that would cause such a simple operation to be so slow? I know it's not just the amount of text being copied, because I tried adding it to a plain String object instead of the RichTextBox and it copied pretty much instantly. Thanks!
Advertisement
Three things you can do to improve responsiveness:

1) Make use of the StringBuilder class to build up your text and set it all at once.

2) Make periodic calls to Application.DoEvents if you have a long running operation.

3) Instead of #2, you could use a BackgroundWorker to set up a separate thread and update the text asynchronously, which lets the UI thread continue to do its work.
Mike Popoloski | Journal | SlimDX
I am guessing that, although building the string bit-by-bit can be very slow and Mike's suggestion on the StringBuilder will vastly improve this, the RichTextBox also does some form of updating when the property changes. Even if every update to the Text property doesn't result in an update, it could result in quite a bit of overhead. Building your string completely first with a StringBuilder, then using that StringBuilder to set the property would also fix this.

In fact, I decided to make a little sample benchmark:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Diagnostics;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;namespace WindowsFormsApplication1{    public class Form1 : Form    {        RichTextBox richTextBox1;        public Form1()        {            InitializeComponent();        }        long AppendToProperty()        {            richTextBox1.Text = string.Empty;            Stopwatch w = new Stopwatch();            w.Start();            for (int i = 0; i < 1000; i++)                richTextBox1.Text += new string('a', i);            w.Stop();            return w.ElapsedMilliseconds;        }        long BuildStringOnly()        {            richTextBox1.Text = string.Empty;            Stopwatch w = new Stopwatch();            w.Start();            StringBuilder sb = new StringBuilder(1024);            for (int i = 0; i < 1000; i++)                sb.Append(new string('a', i));            w.Stop();            return w.ElapsedMilliseconds;        }        void Form1_Load(object sender, EventArgs e)        {            MessageBox.Show(                string.Format(                    "Append to property: {1}{0}" + "StringBuilder (no property): {2}{0}" + "StringBuilder to property: {3}{0}",                    Environment.NewLine, AppendToProperty(), BuildStringOnly(), StringBuilderToProperty()));        }        void InitializeComponent()        {            richTextBox1 = new RichTextBox();            SuspendLayout();            //             // richTextBox1            //             richTextBox1.Location = new Point(50, 64);            richTextBox1.Name = "richTextBox1";            richTextBox1.Size = new Size(238, 154);            richTextBox1.TabIndex = 0;            richTextBox1.Text = "";            //             // Form1            //             AutoScaleDimensions = new SizeF(6F, 13F);            AutoScaleMode = AutoScaleMode.Font;            ClientSize = new Size(411, 377);            Controls.Add(richTextBox1);            Name = "Form1";            Text = "Form1";            Load += Form1_Load;            ResumeLayout(false);        }        long StringBuilderToProperty()        {            richTextBox1.Text = string.Empty;            Stopwatch w = new Stopwatch();            w.Start();            StringBuilder sb = new StringBuilder(1024);            for (int i = 0; i < 1000; i++)                sb.Append(new string('a', i));            richTextBox1.Text = sb.ToString();            w.Stop();            return w.ElapsedMilliseconds;        }    }}


For me, the results were:

53776ms with building on property
2ms to build the StringBuilder
151ms to build the StringBuilder then set it on the property

So the problem is definitely with calling the property so many times. Appending so many strings without a StringBuilder is much slower, but it still only worked out to about 256ms to build the string without a StringBuilder (which still makes the StringBuilder ~128 times faster).

If you are ever combining many strings, even if just like 20, you will want to consider using a StringBuilder. You will also want to never, ever build strings directly onto a property, especially one that is associated with controls. You never know what kind of stuff those properties are doing, or what they could be doing in the future. Combine the strings onto a temporary string or using a StringBuilder first, even if it is just a few strings.

Unfortunately, looking through Reflector, there also seems to be nothing you can do to temporarily prevent the control from updating while you append to the property. So if you are not doing sequential, line-after-line updates but rather your updating ends up just in a very short period of time (and thus becoming the bottleneck), you may have to build your own "buffer" for adding text to this RichTextBox. Create an object that wraps around a StringBuilder, contains a reference to the RichTextBox, and append to that instead. Every second or so, if the object has data buffered, append it to the RichTextBox. This will at least allow you to batch all your calls together for a given period.
NetGore - Open source multiplayer RPG engine
StringBuilder misses the problem entirely. The problem is that every alteration to the Text property of the RichTextBox causes it to come up with the new text, compute everything, generate a new paint event, etc. Just accumulate a whole bunch of text in a string or StringBuilder or whatever and don't update the box quite so often, and it should be fine. You can use P/Invoke to prevent the repaints of the textbox while you're adding text, but I don't recommend using that approach.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
These are all good suggestions above. They are good for a certain class of problem. If you have all of the text already to be appended then that is the way to go, and then only if the text is fairly small. ie.) another problem arises if you are reading the text from a large file. If so, it is likely that you are bound by your disk IO time. And in that case, you'll be better off using a custom GUI control that does not block the process while you are reading text from the file and uploading it to the control.

This is the same approach that Word and the VS.Net editors use. It might be overkill for your situation since it takes a lot of work to implement a control that does this task well. But, it's nice to know that the tools are there if this is what you need.

This topic is closed to new replies.

Advertisement