Showing images on XP-styled buttons.


The other day, I decided to enable visual styles on one of my winform apps and found out that doing so breaks buttons with images. To get around this, I had to write a control which would inherit from the System.Windows.Forms.Button class and then handle the WM_PAINT message and draw the image myself. Below is the code I used.

/*
 * Showing images on XP-styled buttons.
 *
 * Author: Andrew Ma

 * http://blogs.msdn.com/ajma/archive/2005/01/21/358356.aspx
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted.
 *
 * This posting is provided “AS IS” with no warranties, and confers no rights.
 * Use of included code samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm
 */

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ajma
{
 /// <summary>
 /// Represents a Windows button control that will use Visual Styles by default. This button will also draw images if specified.
 /// </summary>
 public class Button : System.Windows.Forms.Button
 {
  /// <summary>
  /// WM_PAINT message
  /// </summary>
  const int WM_PAINT = 0xF;

  /// <summary>
  /// Initializes a new instance of the Button class.
  /// </summary>
  public Button()
  {
   FlatStyle = FlatStyle.System;
  }

  /// <summary>
  /// Overridden. See <see cref=”Control.WndProc”/>.
  /// </summary>
  /// <param name=”m”>The Windows <see cref=”Message”/> to process. </param>
  protected override void WndProc(ref Message m)
  {
   // let base.WndProc go first
   base.WndProc (ref m);

   // if paiting (and there is a message), draw image on top of button
   if(m.Msg == WM_PAINT && this.Image != null)
   {
    Graphics g = Graphics.FromHwnd(this.Handle);
    switch(this.ImageAlign)
    {
     case ContentAlignment.BottomCenter:
      g.DrawImage(this.Image, centerX(), bottom());
      break;
     case ContentAlignment.BottomLeft:
      g.DrawImage(this.Image, left(), bottom());
      break;
     case ContentAlignment.BottomRight:
      g.DrawImage(this.Image, right(), bottom());
      break;
     case ContentAlignment.MiddleCenter:
      g.DrawImage(this.Image, centerX(), centerY());
      break;
     case ContentAlignment.MiddleLeft:
      g.DrawImage(this.Image, left(), centerY());
      break;
     case ContentAlignment.MiddleRight:
      g.DrawImage(this.Image, right(), centerY());
      break;
     case ContentAlignment.TopCenter:
      g.DrawImage(this.Image, centerX(), top());
      break;
     case ContentAlignment.TopLeft:
      g.DrawImage(this.Image, left(), top());
      break;
     case ContentAlignment.TopRight:
      g.DrawImage(this.Image, right(), top());
      break;
    }
    g.Dispose();
   }
  }

  #region Returns the X/Y points for the image to be drawn
  private int centerX()
  {
   return (this.Width – this.Image.Width) / 2;
  }

  private int centerY()
  {
   return (this.Height – this.Image.Height) / 2;
  }

  private int top()
  {
   return 4;
  }

  private int left()
  {
   return 4;
  }

  private int right()
  {
   return this.Width – this.Image.Width – left();
  }

  private int bottom()
  {
   return this.Height – this.Image.Width – top();
  }
  #endregion
 }
}

This posting is provided “AS IS” with no warranties, and confers no rights.

Comments (9)

  1. G. Man says:

    Thanks, but are you sure it has no bugs? I have found that trying to customize WM_PAINT by first letting the default handler paint and then painting on top of that, causes some graphical glitches. I remember in MFC it always told you do not call the base class paint…

  2. Andrew says:

    So I’m no expert on WM_PAINT and stuff like that. The reason I call the base method is so that it will paint the button first and draw the text. If not, then I would have to draw this myself. The buttonalso varies from theme to theme, so I’d rather let the base method do it. If I run into problems with it, I’ll let you know.

    One thing I’ve noticed is that the image will draw on top of the text. In a normal button, the text would draw over the image.

  3. Dean Harding says:

    Those images come from an ImageList, right? And I’ll bet your Main method looks like this:

    static void Main()

    {

    Application.EnableVisualStyles();

    Application.Run(new MainForm());

    }

    right? The problem is that the handles for image lists are created when the form is constructor, but visual styles are only enabled once the message loop is created. Try this"

    static void Main()

    {

    Application.EnableVisualStyles();

    Application.DoEvents();

    Application.Run(new MainForm());

    }

    And see how it goes…

  4. Andrew says:

    Yup, I do call DoEvents() as well. It still won’t draw the images on the buttons.

    So the only shortcoming I’ve found so far is that it flickers a bit when re-drawing.

    Oh, I also had to handle when the button is disabled to draw a grayscale version of the button.

  5. Last week, I needed to add an image to a button. Not a problem of course, unless you need to switch to…

  6. Last week, I needed to add an image to a button. Not a problem of course, unless you need to switch to…

  7. Last week, I needed to add an image to a button. Not a problem of course, unless you need to switch to…

  8. Last week, I needed to add an image to a button. Not a problem of course, unless you need to switch to…

  9. Last week, I needed to add an image to a button. Not a problem of course, unless you need to switch to…