Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

WinForm ImageButton

4.81/5 (29 votes)
16 Sep 2008CC (ASA 2.5)6 min read 1   10K  
WinForm .NET class for an image button allowing for regular, hover, and depressed images, and allows for text.

Introduction

ImageButton is a simple .NET class for a WinForm version of the web hover button, supporting an "idle" image, hover, and "down" image. It can be really useful when designing custom UIs in .NET.

Background

A general knowledge of overriding, inheriting, hiding, images, and attributes in .NET would be useful if you will be modifying the source. You'll need to know how to work with an image editor to create your buttons, or use a button generator off the web.

Using the Code

With Text in the Image

Before you start working with the code, you'll need to create a set of images - normal, hover, and down - all of which are optional, but all together combined make the best effect.

You'll create something like this:

Normal:ExampleButton.png
Hover: ExampleButtonHover.png
Down:ExampleButtonDown.png

Now add ImageButton.dll from bin/Release as a reference to your project, and drag the ImageButton control to your form. You will have a PictureBox-like control on your form.

Change the NormalImage, HoverImage, and DownImage properties in the Property window to match the created set of images.

Without Text in the Image

You can create an image set without text in it so you will be able to set the text using the VS designer. This means you won't need to create a separate image-set for every button.

Note: There is no word wrap in the text feature, you'll have to add line-breaks (vbCrLf in VB or \n in C#) to the text yourself.

First, create an image set without the button text inside; something like this:

Normal: ExampleButtonA.png
Hover: ExampleButtonHoverA.png
Down:ExampleButtonDownA.png

Then, like before, add the ImageButton control and set the image properties, but this time, set the Text property to the text you want to show, and the Font property to the font you want to use.

How It Works

To create the control, I created my ImageButton class, overriding the PictureBox control and implementing IButtonControl. Implementing IButtonControl will allow the ImageButton to be used, as any other button on the form, as a default button or a cancel button.

Mouse Methods

The concept is simple - we create an image and display it on screen. If the user hovers over the image, we swap the image to the hover image, and if the user holds his mouse down, we change the image to the depressed ("down") image.

So, for this purpose exists the following method overrides:

C#
#region HoverImage
private Image m_HoverImage;
[Category("Appearance")]
[Description("Image to show when the button is hovered over.")]
public Image HoverImage
{
get { return m_HoverImage; }
set { m_HoverImage = value; if (hover) Image = value; }
}
#endregion
#region DownImage
private Image m_DownImage;
[Category("Appearance")]
[Description("Image to show when the button is depressed.")]
public Image DownImage
{
get { return m_DownImage; }
set { m_DownImage = value; if (down) Image = value; }
}
#endregion
#region NormalImage
private Image m_NormalImage;
[Category("Appearance")]
[Description("Image to show when the button is not in any other state.")]
public Image NormalImage
{
get { return m_NormalImage; }
set { m_NormalImage = value; if (!(hover || down)) Image = value; }
}
#endregion
        private bool hover = false;
        private bool down = false;
        protected override void OnMouseMove(MouseEventArgs e)
        {
            hover = true;
            if (down)
            {
                if ((m_DownImage != null) && (Image != m_DownImage))
                    Image = m_DownImage;
            }
            else
                if (m_HoverImage != null)
                    Image = m_HoverImage;
                else
                    Image = m_NormalImage;
            base.OnMouseMove(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            hover = false;
            Image = m_NormalImage;
            base.OnMouseLeave(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.Focus();
            down = true;
            if (m_DownImage != null)
                Image = m_DownImage;
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            down = false;
            if (hover)
            {
                if (m_HoverImage != null)
                    Image = m_HoverImage;
            }
            else
                Image = m_NormalImage;
            base.OnMouseUp(e);
        }

Let's go over them:

OnMouseMove

C#
protected override void OnMouseMove(MouseEventArgs e)
{
    hover = true;
    if (down)
    {
        if ((m_DownImage != null) && (Image != m_DownImage))
            Image = m_DownImage;
    }
    else
        if (m_HoverImage != null)
            Image = m_HoverImage;
        else
            Image = m_NormalImage;
    base.OnMouseMove(e);
}

OnMouseMove is called when the button is moving on the form. We avoided using OnMouseHover, because of the delay before the method is called.

We set the hover boolean to true for other methods to know when the mouse is hovering over the control. Then we check if the mouse button is down or not. If it is, we set the button image to the DownImage if the down image is not null and if the image is not already the down image.

If the mouse isn't being pressed, we check if a hover image exists - if it does, we set the image to the hover image, otherwise, we set it to the normal image.

On the last line, we call the Picturebox's version of OnMouseMove.

OnMouseLeave

C#
protected override void OnMouseLeave(EventArgs e)
{
    hover = false;
    Image = m_NormalImage;
    base.OnMouseLeave(e);
}

This needs a little explanation. :) If the mouse has left the bounds of the control, we set a switch about if the mouse is hovering over the button or not to false, and set the image to the "normal" image. We let the PictureBox take the job from there.

OnMouseDown

C#
protected override void OnMouseDown(MouseEventArgs e)
{
    base.Focus();
    down = true;
    if (m_DownImage != null)
        Image = m_DownImage;
    base.OnMouseDown(e);
}

If the mouse has pressed down on the control, we shift focus to the image button (this is not default behavior, so we must implement it) and set the down boolean to true. If the down image has been set by the designer, we change the image to the down image. Then we call the picturebox's own copy of OnMouseDown.

OnMouseUp

C#
protected override void OnMouseUp(MouseEventArgs e)
{
    down = false;
    if (hover)
    {
        if (m_HoverImage != null)
            Image = m_HoverImage;
    }
    else
        Image = m_NormalImage;
    base.OnMouseUp(e);
}

The mouse button is no longer being pressed, so we set down to false. If we are hovering over the control and have let go, then we set the image to the hover image if it isn't nothing, otherwise, we set it to the "normal image". After that, we tell the PictureBox to keep going and handle OnMouseUp.

Null Values

As you may have noticed, we are checking if the hover and down images are null or not before we change the image, but when dealing with NormalImage, we don't. This is to prevent the hover/down images from sticking when the user isn't hovering or clicking on the control if the user didn't specify a normal image. In the demo application, this is demonstrated in Example F.

Text

The PictureBox control has Text and Font properties as required by the Control class, but they are not implemented and are hidden in the Property window. We can change this so that text is rendered:

C#
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("The text associated with the control.")]
public override string Text
{
    get
    {
        return base.Text;
    }
    set
    {
        base.Text = value;
    }
}
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[Category("Appearance")]
[Description("The font used to display text in the control.")]
public override Font Font
{
    get
    {
        return base.Font;
    }
    set
    {
        base.Font = value;
    }
}
protected override void OnPaint(PaintEventArgs pe)
{
    base.OnPaint(pe);
    if ((!string.IsNullOrEmpty(Text)) && (pe != null) && (base.Font != null))
    {
        SolidBrush drawBrush = new SolidBrush(base.ForeColor);
        SizeF drawStringSize = pe.Graphics.MeasureString(base.Text, base.Font);
        PointF drawPoint;
        if (base.Image != null)
            drawPoint = new PointF(base.Image.Width / 2 - drawStringSize.Width / 2, 
		base.Image.Height / 2 - drawStringSize.Height / 2);
        else
            drawPoint = new PointF(base.Width / 2 - drawStringSize.Width / 2, 
		base.Height / 2 - drawStringSize.Height / 2);
        pe.Graphics.DrawString(base.Text, base.Font, drawBrush, drawPoint);
    }
}
protected override void OnTextChanged(EventArgs e)
{
    Refresh();
    base.OnTextChanged(e);
}

Overrides

The PictureBox hides the Text and Font properties from the Property window. To get around this, we create "dummy" overrides for the Text and Font properties which simply set the base class properties, but assign them the Browsable and DesignerSerializationVisibility attributes which will tell the Designer to "notice" these properties.

Painting

Since the PictureBox control from which we are inheriting does not render the text, we must add the code to paint the text on the button, in OnPaint. First we call the PictureBox base class method for OnPaint, which handles painting of the image and everything else. After that, we paint the text on top of the painted image in the middle, by measuring the size of the Text when in the selected Font, and comparing it to the size of the ImageButton, to find where to begin painting the text.

OnTextChanged

When the text of the control is changed, we must repaint the control. Therefore, we override the OnTextChanged method, and in it add the call to the Refresh method (which is inherited from PictureBox), which repaints the button.

Hiding Properties

There are some properties which are not as useful for the ImageButton as the PictureBox, and we want to hide them from the Property window. To do this, we do the opposite of what we did with the Text and Font properties, using the code below:

C#
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Image Image { get { return base.Image; } set { base.Image = value; } }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new ImageLayout BackgroundImageLayout {
	get { return base.BackgroundImageLayout; }
	set { base.BackgroundImageLayout = value; } }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Image BackgroundImage {
	get { return base.BackgroundImage; }
	set { base.BackgroundImage = value; } }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new String ImageLocation {
	get { return base.ImageLocation; }
	set { base.ImageLocation = value; } }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Image ErrorImage {
	get { return base.ErrorImage; }
	set { base.ErrorImage = value; } }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Image InitialImage {
	get { return base.InitialImage; }
	set { base.InitialImage = value; } }
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new bool WaitOnLoad {
	get { return base.WaitOnLoad; }
	set { base.WaitOnLoad = value; } }

Description Changes

The SizeMode and BorderStyle properties mention PictureBox instead of ImageButton. To solve this, we simply use attributes and "dummy" properties to change the description of the properties with this code:

C#
[Description("Controls how the ImageButton will handle
			image placement and control sizing.")]

public new PictureBoxSizeMode SizeMode {
	get { return base.SizeMode; }
	set { base.SizeMode = value; } }

[Description("Controls what type of border the ImageButton should have.")]

public new BorderStyle BorderStyle {
	get { return base.BorderStyle; }
	set { base.BorderStyle = value; } }

IButtonControl

We must also implement IButtonControl. This is simple to do, all we must do is implement the methods as such:

C#
private bool isDefault = false;

        private bool isDefault = false;
        private DialogResult m_DialogResult;
        public DialogResult DialogResult
        {
            get
            {
                return m_DialogResult;
            }
            set
            {
                m_DialogResult = value;
            }
        }

        public void NotifyDefault(bool value)
        {
            isDefault = value;
        }

        public void PerformClick()
        {
            base.OnClick(EventArgs.Empty);
        }

Keyboard Methods

We must implement keyboard events so that the user can use the spacebar and the enter key to "click" the button, just like any other button on any other Windows form.

C#
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private bool holdingSpace = false;
public override bool PreProcessMessage(ref Message msg)
{
    if (msg.Msg == WM_KEYUP)
    {
        if (holdingSpace)
        {
            if ((int)msg.WParam == (int)Keys.Space)
            {
                OnMouseUp(null);
                PerformClick();
            }
            else if ((int)msg.WParam == (int)Keys.Escape
                || (int)msg.WParam == (int)Keys.Tab)
            {
                holdingSpace = false;
                OnMouseUp(null);
            }
        }
        return true;
    }
    else if (msg.Msg == WM_KEYDOWN)
    {
        if ((int)msg.WParam == (int)Keys.Space)
        {
            holdingSpace = true;
            OnMouseDown(null);
        }
        else if ((int)msg.WParam == (int)Keys.Enter)
        {
            PerformClick();
        }
        return true;
    }
    else
        return base.PreProcessMessage(ref msg);
}
protected override void OnLostFocus(EventArgs e)
{
    holdingSpace = false;
    OnMouseUp(null);
    base.OnLostFocus(e);
}

Simply put, we trap the message sent to the control if it is a key up or key down event. If it is, we check what key it is. If it's the enter key, we simply invoke a click event. If it's the spacebar, we hold the button down while it's being pressed until:

  1. the user lets go of the spacebar, in which case we perform a click, or 
  2. the user presses Escape, Tab, or the control loses focus, in which case we do not invoke the click event

If it is not the spacebar and it is not the enter key, we let the PictureBox base class method handle the message.

Demo Application

A demo application is included in the ZIP. Here's a screenshot:

imagebuttondemo.png

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License