0

For colour coding reasons, I need a system whereby I often paste a string to a rich text box (instead of the default standard typing). Unfortunately, it often causes a flash, especially if you keep a key held down.

The RTB doesn't seem to support double buffering, but I'm not sure if that would help anyway. Over-riding the on-paint event also appears ineffective. After researching the web, the best 'solution' I've found so far is to use native Windows interop (LockWindowUpdate etc.). This cured the situation where typing beyond the scroll point was absolutely horrible. Unfortunately, there's still a (lesser) flicker generally now.

The below code is immediately compilable (just create a console project and reference System.Windows.Forms and System.Drawing). If you do, press a key, and keep it held down for say 10 lines worth. If you do, you'll notice more and more flicker cropping up. The more you type, the worse the flicker will become.

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace FlickerTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        [DllImport("user32.dll")]
        public static extern bool LockWindowUpdate(IntPtr hWndLock);
        private void rtb_TextChanged(object sender, EventArgs e)
        {
            String s = rtb.Text;
            LockWindowUpdate(rtb.Handle);
            rtb.Text = s;
            rtb.Refresh(); ////Forces a synchronous redraw of all controls
            LockWindowUpdate(IntPtr.Zero);
        }
    }

    //////////////////////////////////////////////////
    // Ignore below:
    static class Program    {
        [STAThread]
        static void Main()      {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }

    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null)) components.Dispose();
            base.Dispose(disposing);
        }
        #region Windows Form Designer generated code
        private void InitializeComponent()
        {
            this.rtb = new System.Windows.Forms.RichTextBox();
            this.SuspendLayout();
            // rtb
            this.rtb.BackColor = System.Drawing.Color.Black;
            this.rtb.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.rtb.ForeColor = System.Drawing.SystemColors.Window;
            this.rtb.Location = new System.Drawing.Point(24, 20);
            this.rtb.Name = "rtb";
            this.rtb.Size = new System.Drawing.Size(609, 367);
            this.rtb.TabIndex = 0;
            this.rtb.Text = "";
            this.rtb.TextChanged += new System.EventHandler(this.rtb_TextChanged);
            // Form1
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1088, 681);
            this.Controls.Add(this.rtb);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }
        #endregion
        private System.Windows.Forms.RichTextBox rtb;
    }
}
Daniel White
  • 3
  • 1
  • 2

2 Answers2

1

I ran into a similar problem recently. The method I chose was to add extension methods to the richtextbox. I like this approach because it's clean, contained, and easily reusable.

This makes pausing the redraw is as simple as

richtextbox.SuspendDrawing();

and resuming

richtextbox.ResumeDrawing()

public static class RichTextBoxExtensions
{
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
    private const int WM_SETREDRAW = 0x0b;

    public static void SuspendDrawing(this System.Windows.Forms.RichTextBox richTextBox)
    {
        SendMessage(richTextBox.Handle, WM_SETREDRAW, (IntPtr)0, IntPtr.Zero);
    }

    public static void ResumeDrawing(this System.Windows.Forms.RichTextBox richTextBox)
    {
        SendMessage(richTextBox.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero);
        richTextBox.Invalidate();
    }
}
Jason L.
  • 1,125
  • 11
  • 19
  • Unfortunately, it seems that this does not always work. I still get a noticeable flicker when utilizing this solution. – fakedad Feb 02 '16 at 07:07
  • @fakedad Make sure you do all your updates before resuming. Also, you may need to set the RichTextbox to allow double buffering as well. – Jason L. May 08 '17 at 21:00
1

You shouldn't need to refresh the RTB for each key-press which is what happens when you handle TextChanged without any Application.DoEvents() call.

I've found the best way to get acceptable performance is to process the KeyDown event instead. If the keycode (with modifier) is a printable character I start a timer whose Tick event handler checks the text of the richtextbox after say 100ms.

Because when the KeyDown event is fired, the typed char hasn't been printed, you can actually modify text-color for this char within this event handler by setting the SelectionColor property (without changing the selection in code), so you don't need to freeze the control. Don't try doing too much processor-intensive stuff in this though as you'll get responsiveness issues.

Finally, modifying RichTextBox behaviour is a sprial, as soon as you deviate from the norm you will start to use custom code for all sorts of tasks (like copy/paste undo/redo and scrolling) that would normally be performed using the controls own features. You then have a decision to make on whether to continue yourself or go for a 3rd party editor. Note also that though you've started using the Windows API already, there will be much more of this, for scrolling, printing and paint events in particular, though there are good online resources documenting these.

Don't let this discourage you, just be aware what the future may hold if your application requirements grow.

pgfearo
  • 2,087
  • 1
  • 22
  • 27
  • Just to get out the way first; 100ms would be too slow for my needs - I need instant feedback. It needs to appear visually at the same time as when the user presses a key. I have just discovered that even the single command rtf.Refresh() or rtb.Invalidate() in the text changed event or keydown/press event still causes flicker. – Daniel White Jun 07 '11 at 13:01
  • @Daniel White. 100ms is the timer interval, after which you start parsing properly, not the keyboard to screen response time which will be a lot less (say 10ms), even though 200ms equates to 5 characters per second which is about the max of a speed typist. Using a timer which is reset on each keystroke ensures that when a key is pressed and held you only parse once the key has been lifted. To prevent flicker, try StopRepaint [answer here](http://stackoverflow.com/questions/192413/how-do-you-prevent-a-richtextbox-from-refreshing-its-display). – pgfearo Jun 24 '11 at 04:19