RichTextBox syntax highlighting in real time--Disabling the repaint
Asked Answered
U

3

13

I'm creating a function that takes a RichTextBox and has access to a list of keywords & 'badwords'. I need to highlight any keywords & badwords I find in the RichTextBox while the user is typing, which means the function is called every time an editing key is released.

I've written this function, but the words and cursor in the box flicker too much for comfort.

I've discovered a solution--to disable the RichTextBox's ability to repaint itself while I'm editing and formatting its text. However, the only way I know to do this is to override the "WndProc" function and intercept (what I've been about to gather is) the repaint message as follows:

protected override void WndProc(ref System.Windows.Forms.Message m)
{
    if (m.Msg == 0x00f) {
         if (paint)
            base.WndProc(ref m);
         else
            m.Result = IntPtr.Zero;
    }
    else
         base.WndProc(ref m);
}

Where the boolean 'paint' is set to false just before I start highlighting and to true when I finish. But as I said, the function I make must take in a RichTextBox; I cannot use a subclass.

So, is there a way to disable the automatic repainting of a RichTextBox 'from the outside?'

Unfrequented answered 19/7, 2010 at 15:19 Comment(0)
C
27

It is an oversight in the RichTextBox class. Other controls, like ListBox, support the BeginUpdate and EndUpdate methods to suppress painting. Those methods generate the WM_SETREDRAW message. RTB in fact supports this message, but they forgot to add the methods.

Just add them yourself. Project + Add Class, paste the code shown below. Compile and drop the control from the top of the toolbox onto your form.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

class MyRichTextBox : RichTextBox {
    public void BeginUpdate() {
        SendMessage(this.Handle, WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);
    }
    public void EndUpdate() {
        SendMessage(this.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero); 
        this.Invalidate();
    }
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    private const int WM_SETREDRAW = 0x0b;
}

Or P/Invoke SendMessage directly before/after you update the text.

Capsulize answered 19/7, 2010 at 16:21 Comment(3)
This addition to the class does not work for me. It causes some graphics problems and eventually the control to stop working entirely, so that you cannot even scroll the cursor. FYI.Yaekoyael
Pretty standard failure mode when updating the control from a worker thread.Capsulize
Hans, I am relitively new to this stuff. What are you saying/implying with the above? That following the above class extension, that updates to the text box should be done on a seperate thread? Thanks for your time.Yaekoyael
C
6

I haven't accumulated enough points to amend Hans' recommendation. So I added this Answer to mention that it may be necessary to request a repaint by calling InvalidateRect. Some Begin/End Update implementations do this automatically upon the final release of the update lock. Similarly in .Net, Control.Invalidate() can be called which invokes the native InvalidateRect function.

MSDN: Finally, the application can call the InvalidateRect function to cause the list box to be repainted.

See WM_SETREDRAW

Compliance answered 9/1, 2011 at 21:42 Comment(1)
I was having the same issues that Killercam was describing - adding an Invalidate() after re-enabling drawing fixed it.Chambers
K
0

Your best bet to accomplish what you are trying to do is to create a multithreaded application. You'll want to create one thread that checks the text against your list. This thread will put any instances it finds into a queue. You'll also want to create another thread that does the actual highlighting of the words. Because you'll need to use BeginInvoke() and Invoke() to update the UI, you'll want to make sure you throttle the rate at which this gets called. I'd so no more then 20 times per second. To do this, you'd use code like this:

DateTime lastInvoke=DateTime.Now;

if ((DateTime.Now - lastInvoke).TotalMilliseconds >=42)
{
    lastInvoke=DateTime.Now;
    ...Do your highlighting here...
}

This thread will check your queue for words that need to be highlighted or re-highlighted and will constantly check the queue for any new updates. Hope this makes sense!

Karyotype answered 19/7, 2010 at 15:31 Comment(2)
Thanks, I might try that if I can't find an answer to my question. What I'm doing right now works perfect if I can disable the repaint while I'm highlighting.Unfrequented
Yeah I know its not a direct answer to your question, but thought you'd appreciate the input :) This method is how programs, such as Microsoft Word work.Karyotype

© 2022 - 2024 — McMap. All rights reserved.