Click here to Skip to main content
15,891,473 members
Articles / Desktop Programming / MFC
Article

Images on XP-Style Buttons

Rate me:
Please Sign up or sign in to vote.
4.56/5 (36 votes)
27 Oct 20043 min read 398K   5.5K   190   72
A .NET Windows Forms Button using Visual Styles on Windows XP, that can display an image.

Introduction

One thing I love best about XP is the capability to have themed controls. You can even load new themes much like it's been possible with most window managers for the X Window System.

These themed controls can be used with .NET Windows Forms by supplying a manifest and – for some controls like Button – setting the FlatStyle property to FlatStyle.System.

Unfortunately, setting the FlatStyle property of a Button control to FlatStyle.System makes it impossible to display an image on the face of the button. Somehow, setting the Image property has no effect on these controls.

Background

A number of workarounds have been proposed, e.g., this one by MalteseFalcon. All approaches known to me use owner-drawn buttons to mimic the built in visual styles of XP but provide no ability to match third party visual styles.

The code snippet in this article makes it possible to have images on your XP-themed buttons while retaining the capability to adapt to custom visual styles.

The capability to draw controls using different visual styles was introduced by Microsoft with comctl32.dll, version 6, which has been shipping with the newest versions of Windows, i.e., Windows XP and Windows Server 2003. With this version, a number of new API calls have been introduced, among them is BCM_SETIMAGELIST which assigns an image list to a button control. The code in this article uses Platform Invocation Services (PInvoke) to use the BCM_SETIMAGELIST message.

The BCM_SETIMAGELIST message has another interesting property: if you supply more than one image in the image list, you can have the button display a different image for each of a number of states the button is in:

  • Normal
  • Hover
  • Pressed
  • Disabled
  • Focused

This behavior might be achieved using events as well, but using BCM_SETIMAGELIST, you get it without having to code the event handlers.

Using the code

The code wraps the System.Windows.Forms.Button class in a class called ImageButton. You can use this class just like a normal button except for the additional overloaded method SetImage. If you just want to display an image on the face of the button, you can use the method like this:

C#
Bitmap searchBitmap = new Bitmap("Search_16X16_32bpp.png");
ImageButton searchImageButton = new ImageButton();
searchImageButton.SetImage(searchBitmap);
...

If you want more control over the alignment, padding, or set images for different button states, use the overloaded methods:

C#
Bitmap goBitmap = new Bitmap("Go_48X48_32bpp.png");
Bitmap goHoverBitmap = new Bitmap("Go!_48X48_32bpp.png");
Bitmap goDisabledBitmap = new Bitmap("GoDisabled_48X48_32bpp.png");
ImageButton goImageButton = new ImageButton();
goImageButton.SetImage(goBitmap, goHoverBitmap, goBitmap, goDisabledBitmap, 
                       goBitmap, ImageButton.Alignment.Center);
...

Images with alpha channel are supported.

Points of Interest

Windows Forms ImageLists have a bug: if you add an Image to an ImageList, the alpha channel is lost. Therefore, the class in this article uses a workaround by resetting the pixel values of the Image after it has been added to the ImageList.

The demo image shows some buttons with the default XP visual style (top) as well as the custom Chaninja RC5 visual style (bottom).

The images in the demo are from the Qute collection originally drawn for Mozilla Firebird by Arvid "Quadrone" Axelsson.

History

  • February 24, 2004: Version 1.1.
    • Added alignment for non-XP-style buttons.
    • Added speed-up to Bitmap copying, contributed by Richard Deeming.
  • February 26, 2004: Version 1.2.
    • Added image scaling suggested by nxtwothou.
  • February 28, 2004: Version 1.3.
    • Added bicubic interpolation for rescaling.
  • May 8, 2004: Version 1.4.
    • Added drop down arrow capability
  • May 14, 2004: Version 1.5.
    • Added ThemedImage property for the designer.
    • Added auto-generation of disabled image (thanks to Carlos Leyva).
  • October 25, 2004: Version 1.6.
    • Fixed themes on/off detection bug (thanks to Leonid Kunin).
    • Added NAnt file.
    • Added I18n :-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) UpdateStar
Germany Germany
Michael Ganss is Managing Director of UpdateStar. UpdateStar offers complete protection from PC vulnerability caused by outdated software. The award-winning UpdateStar offers comfortable software installation, uninstallation, and keeps all of your programs up-to-date. UpdateStar recognizes more than 135,000 software products and lets you know once an update is available for you - for optimized PC security.

Comments and Discussions

 
GeneralBroke out of the box! Pin
alleyes1-Sep-09 3:34
professionalalleyes1-Sep-09 3:34 
GeneralEasier Method Pin
Evit-Morningstar5-Jul-07 5:42
Evit-Morningstar5-Jul-07 5:42 
NewsCode is obsolete for .NET 2.0... Pin
xlouk7-Jul-06 4:13
xlouk7-Jul-06 4:13 
GeneralRe: Code is obsolete for .NET 2.0... Pin
ernie_hudds2-Apr-07 23:54
ernie_hudds2-Apr-07 23:54 
GeneralRe: Code is obsolete for .NET 2.0... Pin
xlouk3-Apr-07 1:24
xlouk3-Apr-07 1:24 
GeneralRe: Code is obsolete for .NET 2.0... Pin
JagdPanther14-Aug-07 5:37
JagdPanther14-Aug-07 5:37 
GeneralRe: Code is obsolete for .NET 2.0... [modified] Pin
DotNetInterest13-Aug-07 7:15
DotNetInterest13-Aug-07 7:15 
GeneralRe: Code is obsolete for .NET 2.0... Pin
xlouk19-Aug-07 22:32
xlouk19-Aug-07 22:32 
Generalbugs in DropdownButton Pin
PunCha21-Apr-06 3:28
PunCha21-Apr-06 3:28 
GeneralI could not get it work with VS 2003 pro Pin
riscy25-Feb-06 5:49
riscy25-Feb-06 5:49 
GeneralRe: I could not get it work with VS 2003 pro Pin
Michael Ganss26-Feb-06 23:38
Michael Ganss26-Feb-06 23:38 
Question"pushed" button style for a dropdown button ? Pin
joebarthib6-Dec-05 4:58
joebarthib6-Dec-05 4:58 
AnswerRe: "pushed" button style for a dropdown button ? Pin
Michael Ganss6-Dec-05 22:08
Michael Ganss6-Dec-05 22:08 
GeneralRe: pushed button style for a dropdown button ? Pin
joebarthib7-Dec-05 23:20
joebarthib7-Dec-05 23:20 
GeneralImageCheckBox Pin
wolfgang_hg28-Nov-05 4:31
wolfgang_hg28-Nov-05 4:31 
Hi everybody,

I modified the code slightly and it resulted in a ImageCheckBox: a checkbox with an image and xp styles.

Here's my code.
The changes are:
-base class is CheckBox
-Property "ImageAlign" is overridden so that it can be set by designer (would be nice to have in "ImageButton", too) !
-Setting the image by property "ThemedImage": current "ImageAlign" value is honored.
-DropDown-Feature removed (makes no sense with a checkbox).

Enjoy it !

Wolfgang

=================================

///
/// A System.Windows.Forms.CheckBox that can have an image on systems that support visual styles.
/// Uses the BCM_SETIMAGE message that is provided by comctl32.dll, version 6.
/// On systems that have a lower version of comctl32.dll, i.e. earlier than Windows XP,
/// falls back to FlatStyle.Standard.
///
///

public class ImageCheckBox: CheckBox
{
///
/// Constructor.
///

public ImageCheckBox()
{
FlatStyle = FlatStyle.System;
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}

///
/// The alignment of an image within a button.
///

public enum Alignment { Left, Right, Top, Bottom, Center };

private const int BCM_SETIMAGELIST = 0x1600 + 2;

[StructLayout(LayoutKind.Sequential)]
private struct BUTTON_IMAGELIST
{
public IntPtr himl;
public RECT margin;
public int uAlign;
}

[DllImport("user32.dll", CharSet=CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref BUTTON_IMAGELIST lParam);

[StructLayout(LayoutKind.Sequential)]
private struct DLLVERSIONINFO
{
public int cbSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformID;
}

[DllImport("comctl32.dll", EntryPoint="DllGetVersion")]
private static extern int GetCommonControlDLLVersion(ref DLLVERSIONINFO dvi);

private int ComCtlMajorVersion = -1;

private Bitmap themedImage;
[Description("The image on the face of the button."), Category("Appearance"), DefaultValue(null)]
public Bitmap ThemedImage
{
get
{
return themedImage;
}

set
{
if (value != null)
{
SetImage(value);
}

themedImage = value;
}
}

///
/// Set the "ImageAlign" property.
///
/// If the property "ThemedImage" is already set the image is re-rendered.
///

[DefaultValue (ContentAlignment.MiddleCenter)]
[Description ("Ausrichtung des Bildes. Wandelt intern auf einen Wert der Enum \"Alignment\" um !")]
public new ContentAlignment ImageAlign
{
get
{
return base.ImageAlign;
}
set
{
Alignment alignment = GetAlignment(value);

base.ImageAlign = value;

//Ist das ThemedImage gesetzt ? Wenn ja, dann in die Checkbox packen !
if (this.themedImage != null)
{
this.SetImage (this.themedImage, alignment);
}
}
}

///
/// Convert a content alignment to a "Alignment".
///

/// <param name="_contentAlignment" />
/// <returns>
private static Alignment GetAlignment (ContentAlignment _contentAlignment)
{
Alignment alignment = Alignment.Center;
// public enum Alignment { Left, Right, Top, Bottom, Center };
if (_contentAlignment == ContentAlignment.MiddleLeft || _contentAlignment == ContentAlignment.TopLeft || _contentAlignment == ContentAlignment.BottomLeft)
alignment = Alignment.Left;
else if (_contentAlignment == ContentAlignment.MiddleRight || _contentAlignment == ContentAlignment.TopRight || _contentAlignment == ContentAlignment.BottomRight)
alignment = Alignment.Right;
else if (_contentAlignment == ContentAlignment.TopCenter)
alignment = Alignment.Top;
else if (_contentAlignment == ContentAlignment.BottomCenter)
alignment = Alignment.Bottom;
else if (_contentAlignment == ContentAlignment.MiddleCenter)
alignment = Alignment.Center;

return alignment;
}

///
/// Convert the Alignment to a content alignment.
///

/// <param name="_alignment" />
/// <returns>
private static ContentAlignment GetContentAlignment(Alignment _alignment)
{
ContentAlignment contentAlignment = ContentAlignment.MiddleCenter;
switch (_alignment)
{
case Alignment.Bottom:
contentAlignment = ContentAlignment.BottomCenter;
break;
case Alignment.Left:
contentAlignment = ContentAlignment.MiddleLeft;
break;
case Alignment.Right:
contentAlignment = ContentAlignment.MiddleRight;
break;
case Alignment.Top:
contentAlignment = ContentAlignment.TopCenter;
break;
case Alignment.Center:
contentAlignment = ContentAlignment.MiddleCenter;
break;
}

return contentAlignment;
}

///
/// Sets the image of the button to the specified value.
/// The image is aligned at the center with no margin.
/// All states of the button show the same image.
///

/// <param name="image" />The images for the button
public void SetImage(Bitmap image)
{
//Use the current "ImageAlign" value !
//SetImage(new Bitmap[] { image }, Alignment.Center, 0, 0, 0, 0);
SetImage(new Bitmap[] { image }, GetAlignment (this.ImageAlign), 0, 0, 0, 0);
}

///
/// Sets the image of the button to the specified value with the specified alignment.
/// All states of the button show the same image.
///

/// <param name="image" />The images for the button
/// <param name="align" />The alignment of the image
public void SetImage(Bitmap image, Alignment align)
{
SetImage(new Bitmap[] { image }, align, 0, 0, 0, 0);
}

///
/// Sets the image of the button to the specified value with the specified alignment and margins.
/// All states of the button show the same image.
///

/// <param name="image" />The images for the button
/// <param name="align" />The alignment of the image
/// <param name="leftMargin" />The left margin of the image in pixels
/// <param name="topMargin" />The top margin of the image in pixels
/// <param name="rightMargin" />The right margin of the image in pixels
/// <param name="bottomMargin" />The bottom margin of the image in pixels
public void SetImage(Bitmap image, Alignment align, int leftMargin, int topMargin, int rightMargin,
int bottomMargin)
{
SetImage(new Bitmap[] { image }, align, leftMargin, topMargin, rightMargin, bottomMargin);
}

///
/// Sets the images of the button to the specified values.
/// The images are aligned at the center with no margin.
///

/// <param name="image" />The images for the button
/// <param name="normalImage" />The normal state image for the button.
/// <param name="hoverImage" />The hover state image for the button.
/// <param name="pressedImage" />The pressed state image for the button.
/// <param name="disabledImage" />The disabled state image for the button.
/// <param name="focusedImage" />The focused state image for the button.
public void SetImage(Bitmap normalImage, Bitmap hoverImage, Bitmap pressedImage,
Bitmap disabledImage, Bitmap focusedImage)
{
SetImage(new Bitmap[] { normalImage, hoverImage, pressedImage,
disabledImage, focusedImage },
Alignment.Center, 0, 0, 0, 0);
}

///
/// Sets the images of the button to the specified values with the specified alignment.
///

/// <param name="image" />The images for the button
/// <param name="normalImage" />The normal state image for the button.
/// <param name="hoverImage" />The hover state image for the button.
/// <param name="pressedImage" />The pressed state image for the button.
/// <param name="disabledImage" />The disabled state image for the button.
/// <param name="focusedImage" />The focused state image for the button.
/// <param name="align" />The alignment of the image
public void SetImage(Bitmap normalImage, Bitmap hoverImage, Bitmap pressedImage,
Bitmap disabledImage, Bitmap focusedImage,
Alignment align)
{
SetImage(new Bitmap[] { normalImage, hoverImage, pressedImage,
disabledImage, focusedImage },
align, 0, 0, 0, 0);
}

///
/// Sets the images of the button to the specified values with the specified alignment and margins.
///

/// <param name="normalImage" />The normal state image for the button.
/// <param name="hoverImage" />The hover state image for the button.
/// <param name="pressedImage" />The pressed state image for the button.
/// <param name="disabledImage" />The disabled state image for the button.
/// <param name="focusedImage" />The focused state image for the button.
/// <param name="align" />The alignment of the image
/// <param name="leftMargin" />The left margin of the image in pixels
/// <param name="topMargin" />The top margin of the image in pixels
/// <param name="rightMargin" />The right margin of the image in pixels
/// <param name="bottomMargin" />The bottom margin of the image in pixels
public void SetImage(Bitmap normalImage, Bitmap hoverImage, Bitmap pressedImage,
Bitmap disabledImage, Bitmap focusedImage,
Alignment align, int leftMargin, int topMargin, int rightMargin,
int bottomMargin)
{
SetImage(new Bitmap[] { normalImage, hoverImage, pressedImage,
disabledImage, focusedImage },
align, leftMargin, topMargin, rightMargin, bottomMargin);
}

private bool generateDisabledImage = false;
[Description("Determines whether the image for the disabled state will be generated automatically from the normal one."), Category("Appearance"), DefaultValue(false)]
public bool GenerateDisabledImage
{
get
{
return generateDisabledImage;
}

set
{
generateDisabledImage = value;
}
}

[DllImport("UxTheme")]
private static extern bool IsThemeActive();

[DllImport("UxTheme")]
private static extern bool IsAppThemed();

private static bool IsVisualStylesEnabled
{
get
{
return OSFeature.Feature.IsPresent( OSFeature.Themes ) && IsAppThemed() && IsThemeActive();
}
}

///
/// Sets the images of the button to the specified value with the specified alignment and margins.
///

/// <param name="images" />The images for the button.
/// <param name="align" />The alignment of the image
/// <param name="leftMargin" />The left margin of the image in pixels
/// <param name="topMargin" />The top margin of the image in pixels
/// <param name="rightMargin" />The right margin of the image in pixels
/// <param name="bottomMargin" />The bottom margin of the image in pixels
public void SetImage(Bitmap[] images,
Alignment align, int leftMargin, int topMargin, int rightMargin,
int bottomMargin)
{
if (GenerateDisabledImage)
{
if (images.Length == 1)
{
Bitmap image = images[0];
images = new Bitmap[] { image, image, image, image, image };
}

images[3] = DrawImageDisabled(images[3]);
}

if (ComCtlMajorVersion < 0)
{
DLLVERSIONINFO dllVersion = new DLLVERSIONINFO();
dllVersion.cbSize = Marshal.SizeOf(typeof(DLLVERSIONINFO));
GetCommonControlDLLVersion(ref dllVersion);
ComCtlMajorVersion = dllVersion.dwMajorVersion;
}

if (ComCtlMajorVersion >= 6 && FlatStyle == FlatStyle.System
&& IsVisualStylesEnabled)
{
RECT rect = new RECT();
rect.left = leftMargin;
rect.top = topMargin;
rect.right = rightMargin;
rect.bottom = bottomMargin;

BUTTON_IMAGELIST buttonImageList = new BUTTON_IMAGELIST();
buttonImageList.margin = rect;
buttonImageList.uAlign = (int)align;

ImageList = GenerateImageList(images);
buttonImageList.himl = ImageList.Handle;

SendMessage(this.Handle, BCM_SETIMAGELIST, 0, ref buttonImageList);
}
else
{
FlatStyle = FlatStyle.Standard;

if (images.Length > 0)
{
Image = images[0];
}

switch (align)
{
//Don't modify value if it is already the correct one (would lead to stack overflows in settings the
//property "ImageAlign") !
case Alignment.Bottom:
if (this.ImageAlign != ContentAlignment.BottomCenter)
this.ImageAlign = ContentAlignment.BottomCenter;
break;
case Alignment.Left:
if (this.ImageAlign != ContentAlignment.MiddleLeft)
this.ImageAlign = ContentAlignment.MiddleLeft;
break;
case Alignment.Right:
if (this.ImageAlign != ContentAlignment.MiddleRight)
this.ImageAlign = ContentAlignment.MiddleRight;
break;
case Alignment.Top:
if (this.ImageAlign != ContentAlignment.TopCenter)
this.ImageAlign = ContentAlignment.TopCenter;
break;
case Alignment.Center:
if (this.ImageAlign != ContentAlignment.MiddleCenter)
this.ImageAlign = ContentAlignment.MiddleCenter;
break;
}
}
}

private System.Drawing.Bitmap DrawImageDisabled(System.Drawing.Image image)
{
System.Drawing.Bitmap active = new Bitmap(image);
System.Drawing.Bitmap disable = new Bitmap(active.Width, active.Height);
System.Drawing.Graphics g = Graphics.FromImage(disable);

g.DrawImage(active, 0, 0);
ControlPaint.DrawImageDisabled(g, active, 0, 0, Color.Empty);
g.Dispose();
return disable;
}

private ImageList GenerateImageList(Bitmap[] images)
{
ImageList il = new ImageList();
il.ColorDepth = ColorDepth.Depth32Bit;

if (images.Length > 0)
{
for (int i = 0; i < images.Length; i++)
{
int ReSizeHeight = this.Height;
int ReSizeWidth = this.Width;

if (ReSizeHeight > 256)
{
ReSizeHeight = 256;
}

if (ReSizeWidth > 256)
{
ReSizeWidth = 256;
}

if (images[i].Width > ReSizeWidth || images[i].Height > ReSizeHeight)
{
double Scaling;
double WidthScaling = ReSizeWidth / (double)images[i].Width;
double HeightScaling = ReSizeHeight / (double)images[i].Height;

if (WidthScaling < HeightScaling)
{
Scaling = WidthScaling;
}
else
{
Scaling = HeightScaling;
}

int newWidth = (int)(images[i].Width * Scaling);
int newHeight = (int)(images[i].Height * Scaling);
Bitmap bm = new Bitmap(newWidth, newHeight);
Graphics graphics = Graphics.FromImage(bm);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(images[i], 0, 0, newWidth, newHeight);
images[i] = bm;
}
}

il.ImageSize = new Size(images[0].Width, images[0].Height);

foreach (Bitmap image in images)
{
il.Images.Add(image);
Bitmap bm = (Bitmap)il.Images[il.Images.Count - 1];

// copy pixel data from original Bitmap into ImageList
// to work around a bug in ImageList:
// adding an image to an ImageList destroys the alpha channel

//WKnauf 25.02.2005: den Unsafe Code auskommentiert, den Safe Code reingenommen !
for (int x = 0; x < bm.Width; x++)
{
for (int y = 0; y < bm.Height; y++)
{
bm.SetPixel(x, y, image.GetPixel(x, y));
}
}

/*
// code below contributed by Richard Deeming
// requires /unsafe compiler flag
// does the same as the code above, albeit a lot faster
Rectangle rc = new Rectangle(new Point(0, 0), image.Size);
BitmapData src = image.LockBits(rc, ImageLockMode.ReadOnly, image.PixelFormat);
BitmapData dst = bm.LockBits(rc, ImageLockMode.WriteOnly, image.PixelFormat);

try
{
unsafe
{
int* pSrc = (int*)src.Scan0;
int* pDst = (int*)dst.Scan0;

for(int row=0; row < bm.Height; row++)
{
for(int col=0; col < bm.Width; col++)
{
pDst[col] = pSrc[col];
}

pSrc += src.Stride >> 2;
pDst += dst.Stride >> 2;
}
}
}
finally
{
bm.UnlockBits(dst);
image.UnlockBits(src);
}*/
}
}

return il;
}
}
QuestionNo XP Style Pin
Greg Osborne26-Oct-05 5:57
Greg Osborne26-Oct-05 5:57 
AnswerRe: No XP Style Pin
Michael Ganss26-Oct-05 21:55
Michael Ganss26-Oct-05 21:55 
QuestionProblem after using ImageButton Pin
RagulKumar29-Aug-05 17:28
RagulKumar29-Aug-05 17:28 
AnswerRe: Problem after using ImageButton Pin
Michael Ganss29-Aug-05 22:00
Michael Ganss29-Aug-05 22:00 
QuestionRe: Problem after using ImageButton Pin
Anonymous31-Aug-05 19:05
Anonymous31-Aug-05 19:05 
AnswerRe: Problem after using ImageButton Pin
Michael Ganss31-Aug-05 22:15
Michael Ganss31-Aug-05 22:15 
AnswerRe: Problem after using ImageButton Pin
Greg Osborne26-Oct-05 6:01
Greg Osborne26-Oct-05 6:01 
GeneralGDI-Exceptions due to unsafe code ?! Pin
wolfgang_hg25-Feb-05 5:51
wolfgang_hg25-Feb-05 5:51 
GeneralRe: GDI-Exceptions due to unsafe code ?! Pin
Michael Ganss25-Feb-05 6:02
Michael Ganss25-Feb-05 6:02 
GeneralRe: GDI-Exceptions due to unsafe code ?! Pin
Joel Lucsy3-Mar-05 9:14
Joel Lucsy3-Mar-05 9:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.