0
votes

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;
    }
}
2

2 Answers

1
votes

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.

1
votes

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();
    }
}