Friday, June 27, 2008
« Retransitioning My Blog to DasBlog | Main | Patching 'A severe error occurred on the... »

I was putting together a small application today that presents data in a TextBox.  Now while this isn't too uncommon :), the text gets added to the TextBox a line at a time via the TextBox's .AppendText() method.  I needed to do this repeatedly and in succession.  As it turns out, an unwelcome side effect of AppendText is that it scrolls the TextBox as the text is appended.  This resulted in the TextBox getting cleared and then 'wiping' down as the text was added.  Not only was this visually unappealing, but I wanted the insertion point to stay at the top of the contents.

To my knowledge, which is quite flawed and limited, there's not a built-in mechanism in .NET that provides this functionality.  Sure, I could override the TextBox control and manage the WndProc method and/or use SetStyle to make the control user drawn.  But I didn't want to do that.  Well, accomplishing this is quite easy.

There are a few ways we can do it:

  1. Use the LockWindowUpdate() API function.
  2. Use SendMessage() API function with the WM_SETREDRAW message.

LockWindowUpdate() is slick and arguably easier to use, but it's intended purpose isn't to suppress the redrawing of controls in this manner.  Plus, you can only have one window locked at a time.  So I threw that one out.

I wrapped the logic to lock and unlock the redrawing of the control in an IDisposable object so I wouldn't have to worry about remembering to clean up after myself.  My class is as follows:

public class LockVisualUpdate : IDisposable {
   public LockVisualUpdate(IWin32Window control) {
      _hWnd = control.Handle;
      SendMessage(control.Handle, WM_SETREDRAW, 0, 0);
   }


   private readonly IntPtr _hWnd;
   private const int WM_SETREDRAW = 0x000B;


   [DllImport("user32.dll")]
   private static extern bool SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);


   [DllImport("user32.dll")]
   private static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);


   public void Dispose() {
      SendMessage(_hWnd, WM_SETREDRAW, 1, 0);
      InvalidateRect(_hWnd, IntPtr.Zero, false);
   }
}

Really simple to use.  To consume it, I simply have to do the following:

using ( new LockVisualUpdate(textBox1) ) {
   textBox1.AppendText(...);
   // ...repeated as often as necessary to populate the control

   // when finished, position the insertion point to the top of the control
   textBox1.SelectionStart = 0;
   textBox1.ScrollToCaret();
}

.NET | C#