The approach I took was to run the formatting logic in BackgroundWorker. I chose this because the format will take a "long" time, more than 1 second or two, so I could not do this in the user interface thread.
Just to redo the problem: each call made by BackgroundWorker for the RichTextBox.SelectionColor installer again raised the TextChanged event, which again triggered the BG stream. In the TextChanged event, I did not find a way to distinguish the event "user typed something" from the event "text formatting". So you can see that it will be an endless progress of change.
Simple approach doesn't work
The general approach ( as suggested by Eric ) is to “disable” the processing of text change events while working in a text change handler. But, of course, this will not work for my case, because text changes (SelectionColor changes) are generated by the background thread. They are not executed as part of the text change handler. Therefore, a simple approach to filtering user-initiated events will not work for my case when the background thread makes changes.
Other attempts to detect user changes
I tried using RichTextBox.Text.Length to distinguish richtextbox changes coming from my formatting stream from richtextbox changes made by the user. If the length didn’t change, I reasoned, then the change was a format change executed by my code, and not editing the user. But getting the RichTextBox.Text property is expensive, and doing this for each TextChange event makes the entire user interface unacceptably slow. Even if it was fast enough, it does not work in the general case, because users also make changes to the format. And, user editing can produce the same length text if it was a typeover operation.
I was hoping to catch and handle the TextChange event ONLY to detect changes coming from the user. Since I could not do this, I changed the application to use the KeyPress event and the Paste event. As a result, I no longer receive false TextChange events due to formatting changes (for example, RichTextBox.SelectionColor = Color.Blue).
Alarm workflow to do its work
Ok, I have a stream that can make formatting changes. It is clear that he does this:
while (forever) wait for the signal to start formatting for each line in the richtextbox format it next next
How can I tell the BG stream to start formatting?
I used ManualResetEvent . When KeyPress is detected, the keystroke handler sets this event (turns it on). A background worker is waiting for the same event. When it turns on, the BG stream turns it off and starts formatting.
But what if working BG already formats? In this case, a new keystroke could change the contents of the text field, and any formatting done so far may now be invalid, so formatting must be restarted. What I really want for the formatting stream looks something like this:
while (forever) wait for the signal to start formatting for each line in the richtextbox format it check if we should stop and restart formatting next next
With this logic, when ManualResetEvent is set (on), the formatting stream detects this and resets it (turns off) and starts formatting. He scans the text and decides how to format it. Periodically, the formatting stream checks ManualResetEvent again. If another keypress event occurs during formatting, the event returns to the alarm state. When the formatter sees that it is retransmitted, the formatter breaks out and starts formatting again from the beginning of the text, for example, Sisyphus. A more intelligent mechanism will restart formatting from the point in the document where the change occurred.
Delayed start formatting
Another twist: I don't want the formatter to start formatting right away with every KeyPress right away. As a human type, the normal pause between keystrokes is less than 600-700 ms. If the formatter starts formatting without delay, it will try to start formatting between keystrokes. Pretty pointless.
Thus, the formatting logic starts its formatting job if it detects a pause in keystrokes for more than 600 ms. After receiving the signal, it waits for 600 ms, and if there were no intermediate keystrokes, the typing stopped and formatting started. If an intermediate change occurs, the formatter does nothing, concluding that the user is still printing. In code:
private System.Threading.ManualResetEvent wantFormat = new System.Threading.ManualResetEvent(false);
Key Press Event:
private void richTextBox1_KeyPress(object sender, KeyPressEventArgs e) { _lastRtbKeyPress = System.DateTime.Now; wantFormat.Set(); }
In the colorizer method, which runs in the background thread:
.... do { try { wantFormat.WaitOne(); wantFormat.Reset(); // We want a re-format, but let make sure // the user is no longer typing... if (_lastRtbKeyPress != _originDateTime) { System.Threading.Thread.Sleep(DELAY_IN_MILLISECONDS); System.DateTime now = System.DateTime.Now; var _delta = now - _lastRtbKeyPress; if (_delta < new System.TimeSpan(0, 0, 0, 0, DELAY_IN_MILLISECONDS)) continue; } ...analyze document and apply updates... // during analysis, periodically check for new keypress events: if (wantFormat.WaitOne(0, false)) break;
The user interface is that when they are entered, formatting does not occur. After entering pauses, formatting starts. If input starts again, formatting stops and waits again.
Disable scrolling during format changes
There was one problem: formatting text in a RichTextBox requires calling RichTextBox.Select () , which calls RichTextBox to automatically scroll to the selected text when RichTextBox has focus. Since formatting occurs at the same time, the user is focused on managing, reading, and possibly editing text, I need a way to suppress scrolling. I could not find a way to prevent scrolling using the public RTB interface, although I found many people in intertubes asking about it. After some experimentation, I found that using a Win32 call to SendMessage () (from user32.dll), sending WM_SETREDRAW before and after Select () can interfere with scrolling to RichTextBox when calling Select ().
Since I used pinvoke to prevent scrolling, I also used pinvoke in SendMessage to get or set the selection or carriage in the text box ( EM_GETSEL or EM_SETSEL ) and set the formatting to the selection ( EM_SETCHARFORMAT ). The pinvoke approach turned out to be a bit faster than using a managed interface.
Responsive Batch Updates
And since preventing scrolling caused some overhead, I decided to make changes to the document. Instead of highlighting one continuous section or word, the logic stores a list of changes to the highlight or format. Each time so often, perhaps 30 changes are applied at a time to the document. Then it clears the list and returns to the analysis and the order of what format changes should be made. It is fast enough that the text input in the document is not interrupted by applying these change batches.
The result is that the document is automatically formatted and painted in discrete pieces when there is no text input. If there is enough time between user keystrokes, the entire document will eventually be formatted. This is up to 200 ms for an XML document with a size of 1 thousand, Perhaps 2 s for a document of 30 thousand, or 10 s for a document of 100 thousand. If the user edits the document, any formatting is interrupted, and formatting starts again and again.
Phew!
I am amazed that it is as simple as formatting a richtextbox, while the user types into it, it is so involved. But I couldn't think of anything simpler so as not to block the text box, but avoided the weird scroll behavior.
You can view the code for the thing described above.