Thursday, August 28, 2008

Overriding ListBox's DefWndProc to Create a Gray Free Read-Only Control

Inside every Windows Forms control, there's a mysterious method called "DefWndProc". According to MSDN, this method "Sends the specified message to the default window procedure.". Here's how it works:

First, you click a button on your mouse, or press something on your keyboard. Windows processes your request, and sends your request, via the Message structure to a method called "WndProc". WndProc will process the message, and is responsible for raising the events you can attach to in your code, such as the OnMouseClick and OnKeyPress events. Before raising these events, it calls DefWndProc, for Windows to handle the default behavior on the control.

Handling the default behavior of the control includes doing things like painting a new character in a textbox, making a button look pressed when the user clicks it, drawing a check in a checkbox when the user clicks it, and highlighting and selecting items in a ListBox. The beauty of overriding DefWndProc is that, while the events of the control will still be raised and your custom actions will still be executed, you can still stop the default behavior from being executed.

One thing you should know before you start, however, is that the Message structure is not very user-friendly. The key to it, however, is the "Msg" property, which indicates the actual action that the user has performed. PInvoke.net has posted an enumeration including windows messages, and some of their descriptions so you can figure out which messages mean what. But the simplest (and in my opinion, most educational) way to learn these messages, however, is to insert a Debug.WriteLine in your overridden DefWndProc, and then watch the messages in Visual Studio's output window as you debug your application. You'll probably be surprised at the sheer number of messages that are sent from Windows to your control whenever you do anything relating to your control. Experimenting with blocking these messages in DefWndProc can give you great control over your control.

So, in the example below, I've extended the ListBox control to add a "ReadOnly" property, and also to block any messages from the UI that might affect my control from being selected. Essentially, this blocks the user out of the control selection process, while allowing the code to do the selecting and deselecting. It will allow the user to scroll through the ListBox (unlike the effect that setting Enabled to false has on the ListBox), and it will prevent the ForeColor from going gray.



   1:  using System.Windows.Forms;

   2:   

   3:  namespace CustomControls

   4:  {

   5:      public class ReadOnlyListBox : ListBox

   6:      {

   7:          private bool _readOnly = false;

   8:   

   9:          public bool ReadOnly

  10:          {

  11:              get { return _readOnly; }

  12:              set { _readOnly = value; }

  13:          }

  14:   

  15:          protected override void DefWndProc(ref Message m)

  16:          {

  17:              // If ReadOnly is set to true, then block any messages

  18:              // to the selection area from the mouse or keyboard.

  19:              // Let all other messages pass through to the

  20:              // Windows default implementation of DefWndProc.

  21:              if (!_readOnly

  22:                  ((m.Msg <= 0x0200 || m.Msg >= 0x020E)

  23:                  && (m.Msg <= 0x0100 || m.Msg >= 0x0109)

  24:                  && m.Msg != 0x2111

  25:                  && m.Msg != 0x87))

  26:              {

  27:                  base.DefWndProc(ref m);

  28:              }

  29:          }

  30:      }

  31:  }