Flat Style Combo Boxes

Customise any Combo Box with a VS.NET or Office XP Style

Flat Combo Box Demonstration

This article provides a class which you can attach to any .NET ComboBox control to allow it to render with a VS.NET or Office XP flat style by subclassing the messages associated with the control (VB.NET and C# code provided).

Making a Combo Box Flat

If you've seen the elegant flat combo boxes which appear in VS.NET and Microsoft Office Command Bars, you might have wondered how easy it is to implement this sort of functionality in your own application. The first thing to do in these cases is usually to do a bit of, erm, reverse-engineering to see how Microsoft achieved the effect. The best tool for this sort of investigation is Spy++, which comes with some variants of VS.NET, although if you don't have it you can also use the Simple Windows Spy utility from this site. Point the tool at one of the combo boxes and...

Ah. As with many aspects of the Microsoft Command Bars control, the combo boxes are not in fact regular controls at all. In fact, the control you see is a composite, using a RichTextBox for the edit portion, drawing the combo drop-down button directly onto the toolbar and using a new custom control with the class name "OfficeDropDown" for the drop-down portion. This kind of approach probably isn't too tricky if you have access to the Windows source code, but can be a more sticky if you want to create the effect yourself.

Another way is to start looking at how a standard combo box draws itself and see if it can be customised by subclassing some of the logic. Any control which needs to draw on screen has to respond to the WM_PAINT Windows message in order to paint itself. However, that's not the only way to paint a control. Controls can also obtain their drawing surface outside of WM_PAINT (using, for example, GetDC) and perform some sneaky redrawing there as well. But provided all the points at which drawing occurs can be captured, and enough information about the state of the control can be worked out at each step then you can overdraw what the control attempts to draw with a customised version.

This is the approach taken by the FlatControl class presented here.

Using the FlatControl class

The class has been designed so it is completely independent of the combo box that it is attached to. This helps to make it convenient to apply to any type of Combo Box, even if the combo box is an extended version of the standard System.Windows.Forms.ComboBox.

To use it, you create an instance of the FlatControl class and then use the Attach method to attach it to the combo box:

using vbAccelerator.Components.Controls;

   FlatControl flatComboBox = new FlatControl();

   private void frm_Load(object sender, System.EventArgs e)
   {
      flatComboBox.Attach(cbo);
   }

Note that it is important that the handle of the combo box has been created and the combo box has its correct parent prior to using the attach method. For this reason the code above is being performed in the Load event of the form. Another thing to watch for is that changing some form or combobox properties causes the control to be recreated from scratch (typical examples are the DropDownStyle of a combo box or the ShowInTaskBar property of a Form). If this occurs you will need to re-attach the class. Ideally, you should try to avoid changing these types of properties once controls or forms have been created, since they result in flicker and make the user interface look less professional.

To convert all the combo box controls in a form, you can use the code shown in the sample to automatically enumerate all ComboBox type controls and store an array of flat control instances:

   private ArrayList flatComboBoxList = new ArrayList();

   ...  

   FlatControl flatComboBox;
   foreach (Control ctl in this.Controls)
   {
      if (ctl.GetType().IsAssignableFrom(comboBox1.GetType()))
      {
         flatComboBox = new FlatControl();
         flatComboBox.Attach(ctl);
         flatComboBoxList.Add(flatComboBox);
      }
   }

Implementation

The FlatControl class consists of three parts: the main class, which is responsible for drawing the combo box, and two internal child classes. The internal child classes are a FlatControlTextBox class, which is responsible for detecting focus and mouse events in the TextBox portion of the control and the FlatComboParent class, which is responsible for detecting when the combo box has closed up. I'll describe these briefly in turn.

FlatControl

This class performs most of the work in the control. It extends the System.Windows.Forms.NativeWindow class so it can override WndProc and act as a subclass on the ComboBox it is attached to. During WndProc processing, the class responds to WM_PAINT messages to allow the control to be repainted after Windows has painted it. It also responds to messages WM_SETFOCUS and WM_KILLFOCUS and WM_MOUSEMOVE so it can repaint the control with the correct border.

To achieve overpainting, the control performs the following steps:

  1. Get the hDC of the ComboBox

    This is achieved using the Windows API GetDC call:

       [DllImport("user32")]
       private static extern IntPtr GetDC(
          IntPtr hWnd);
    
       IntPtr hDC = GetDC(this.Handle);
    
  2. Convert the hDC into a Graphics object

    Graphics gfx = Graphics.FromHdc(hDC);

  3. Perform Drawing

    Since we now have a managed code Graphics object, the drawing proceeds the standard .NET Framework techniques. Refer to the code for the details: basically all that is done is to draw the border of the control and the drop-down button in the correct positions.

  4. Clean up the hDC Handle

    Any call to GetDC must be matched with a call to ReleaseDC, otherwise a resource leak will occur. This is also a good time to dispose of the graphics object.

       [DllImport("user32")]
       private static extern int ReleaseDC(
          IntPtr hWnd, 
          IntPtr hdc);
    
       gfx.Dispose();
       ReleaseDC(this.Handle, hDC);
    

One thing I noticed in the first version of this class was that if a Windows Theme was applied to the application then having ComCtl32.DLL redrawing over the combo box as well led to some flicker and a need to draw the combo box more frequently. Toprevent this, the code removes the Windows Theme from the combo box if it is applied:

   [DllImport("user32", CharSet=CharSet.Unicode)]
   private static extern int SetWindowTheme (
      IntPtr hWnd, 
      [MarshalAs(UnmanagedType.LPWStr)]
      String pszSubAppName, 
      [MarshalAs(UnmanagedType.LPWStr)]
      String pszSubIdList);

   private void RemoveTheme(
      IntPtr handle)
   {   
      bool isXp = false;
      if (System.Environment.Version.Major > 5)
      {
         isXp = true;
      }
      else if ((System.Environment.Version.Major == 5) &&
         (System.Environment.Version.Minor >= 1))
      {
         isXp = true;
      }
      if (isXp)
      {
         SetWindowTheme(handle, " ", " ");
      }
   }

FlatComboTextBox

If the Combo Box has the DropDownStyle.DropDown style, then there is also a TextBox included in the control. This class attaches a subclass using the same NativeWindow technique to the text box and forwards focus and mouse messages to the main class.

FlatComboParent

Finally, when drawing the dropdown button, you need to know when the combo box is dropped down or closed up. Combo boxes notify their parent when they are closed up using a WM_COMMAND message with a CBN_CLOSEUP notification code. This class subclasses that message to ensure it is forwarded on to the main class.

Conclusion

This article provides a class which can be used to customise standard ComboBox controls to give them their own. With a little patience, you should also be able to modify the drawing code to generate combo boxes with custom drop-down buttons and borders to match the skin of an application.