Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom Bitmap Button Using C#

0.00/5 (No votes)
3 Mar 2005 1  
An article on creating a bitmap button

Sample Image - maximum width is 600 pixels

Introduction

The motivation behind the custom bitmap control was to allow different bitmap images to be displayed for each of the button states. This includes disabled, normal, mouse over, and button pressed. In addition to the button graphics, it was important to include button text and control the alignment of the text with regards to the button image. It follows an XP style look and feel with some embellishments that are unique to it.

Using the Code

The code can be segmented into 3 main sections: data, rendering, and events.

  • Data: The private variables hold the state and property settings. For the most part, this code follows the practice of properties referencing or changing the values of member variables. Each property is described in the Properties table below.
  • Rendering: The rendering of the button is facilitated by several methods; however, the OnPaint method is the driver which calls the other paint methods for button rendering.
  • Events: A number of methods are overridden to tackle the processing of events and managing the state of the button. The methods are OnMouseDown, OnMouseUp, OnMouseLeave, OnMouseMove, OnEnabledChanged, OnLostFocus.

When viewing the source code, the sections can be easily found, since the #region keyword is used for code separation.

Data

First, let's explore the properties.

Bitmap Button Properties

BackColor Background color of the button
BorderColor The color of the thin one pixel width border surrounding the button
Font Font used to render the text
ForeColor Color of button text
ImageAlign Specifies the alignment of the image
ImageBorderColor If ImageBorderEnabled is true, then this property contains the color of the rendered image border. In addition, the StretchImage property must be false.
ImageBorderEnabled true if to render an image border, otherwise false
ImageDropShadow true, if to render a shadow around the image border
ImageFocused image used to render when button has focus and is in a normal state
ImageInactive image used when button is disabled. Note, if a image is not defined, a gray scale version of the normal image is used in substitution
ImageMouseOver image used when the mouse is over the button, but the button is not pressed
ImageNormal image used when the button is it its normal state. Note, this image must be set for an image button
ImagePressed image used when button is pressed
InnerBorderColor color of the inner border while button is in its normal state
InnerBorderColor_Focus color of the inner border when the button has focus
InnerBorderColor_MouseOver color of the inner border when the mouse is over a button
OffsetPressedContent If this is set to true and the button is pressed, the contents of the button is shifted.
Padding It holds the pixel padding amount between each of the button contents. This is the space between the image, text, and border.
StretchImage If true, it indicates to stretch the current image across the button.
Text The text to be displayed in the button
TextAlignment Defines the alignment of the text
TextDropShadow If true, the text casts a shadow

All of the properties have been added to the appearance category in the property page. The below picture is a snapshot of them.

Rendering

Rendering of the button is performed by the OnPaint method. This, in turn, calls several routines to handle the rendering particulars of the button.

  • CreateRegion: Creates a see-through rounded button edge for the control
  • paint_Background: Renders the background of the button
  • paint_Text: Renders the text and text drop shadow
  • paint_Border: Renders the 1 pixel with border around the button
  • paint_InnerBorder: Renders the 2 pixel width inner border
  • paint_FocusBorder: Renders the 1 pixel width dashed focus border within the button

See the below code snippet for details:

/// <summary>
/// This method paints the button in its entirety.
/// </summary>
/// <param name="e">paint arguments use to paint the button</param>
protected override void OnPaint(PaintEventArgs e)
{				
	CreateRegion(0);			
	paint_Background(e);
	paint_Text(e);
	paint_Image(e);			
	paint_Border(e);
	paint_InnerBorder(e);
	paint_FocusBorder(e);
}

Painting the background can be of some interest. The approach that was taken allows for a gradient background interpolation between multiple colors (meaning more than 2 colors). First, a blend object needs to be initialized with an array of colors, and the position of interpolation. Next, the gradient brush can be created as usual. The final step involves linking the blend object to the brush. This is accomplished by setting the InterpolationColors property of a brush.

The following is an example interpolating multiple colors:

Color[] ColorArray = new Color[]{
   System.Drawing.Color.White,
   System.Drawing.Color.Yellow,
   System.Drawing.Color.Blue,
   System.Drawing.Color.Green,               
   System.Drawing.Color.Red,
   System.Drawing.Color.Black};				
float[] PositionArray  = new float[]{0.0f,.15f,.40f,.65f,.80f,1.0f};
//
// create blend variable for the interpolate the colors
//
System.Drawing.Drawing2D.ColorBlend blend
                                = new System.Drawing.Drawing2D.ColorBlend();
blend.Colors    = ColorArray;
blend.Positions = PositionArray;
//
// create vertical gradient brush
//
System.Drawing.Drawing2D.LinearGradientBrush brush
                = new System.Drawing.Drawing2D.LinearGradientBrush(rect, 
                      this.BackColor,Blend(this.BackColor,this.BackColor,10),
                      System.Drawing.Drawing2D.LinearGradientMode.Vertical);
brush.InterpolationColors = blend;
//
// fill the rectangle
//
g.FillRectangle(brush, rect);
//
// release resources
//
brush.Dispose();

For rendering the text, I used System.Drawing.DrawString method. The tricky part was determining where to draw the text. Because of the amount of code, that functionality was placed in helper functions to keep it from cluttering the paint_Text method. One point of interest with this method is that it implements the drop shadow functionality. This simply involves creating brushes with alpha components, and drawing the text as usual.

//
// paint text shadow
//
if(TextDropShadow)
{
	System.Drawing.Brush TransparentBrush0
          = new System.Drawing.SolidBrush( System.Drawing.Color.FromArgb(50, 
                System.Drawing.Color.Black  ) ) ;
	System.Drawing.Brush TransparentBrush1
           = new System.Drawing.SolidBrush( System.Drawing.Color.FromArgb(20, 
                System.Drawing.Color.Black  ) ) ;

	e.Graphics.DrawString(this.Text,this.Font,
                                               TransparentBrush0,pt.X,pt.Y+1);
	e.Graphics.DrawString(this.Text,this.Font, 
                                               TransparentBrush0,pt.X+1,pt.Y);
			
	e.Graphics.DrawString(this.Text,this.Font, 
                                            TransparentBrush1,pt.X+1,pt.Y+1);	
	e.Graphics.DrawString(this.Text,this.Font, 
                                            TransparentBrush1,pt.X,pt.Y+2);	
	e.Graphics.DrawString(this.Text,this.Font, 
                                            TransparentBrush1,pt.X+2,pt.Y);	
	TransparentBrush0.Dispose();
	TransparentBrush1.Dispose();	
}

Painting the image was a rather straight forward process. However, I did experience some difficulties when using the below method. It worked properly using a bitmap created by the resource editor, but unfortunately, failed with a 24 bit image created by a 3rd party paint program. The workaround involved calling a different DrawImage method. It is probably slower, but until I understand what the issue is, it will have to work for now.

// FAILED
g.DrawImage(image,rect.Left,rect.Top)
// WORKAROUND
g.DrawImage(image,rect, 0, 0 ,image.Width,image.Height, GraphicsUnit.Pixel);

Painting borders with interpolating colors was not difficult either. You go through the same process of creating a gradient brush as before. The gradient brush is passed as a parameter when creating the pen object. The below code snippet is an example of this process.

....
//
// create brush and pens
//
System.Drawing.Drawing2D.LinearGradientBrush brush
            = new System.Drawing.Drawing2D.LinearGradientBrush(rect,  
                  this.BackColor,Blend(this.BackColor,this.BackColor,10), 
                  System.Drawing.Drawing2D.LinearGradientMode.Vertical);
brush.InterpolationColors = blend;
System.Drawing.Pen pen0 = new System.Drawing.Pen(brush,1);
//
// draw line 0
//
g.DrawLine(pen0 , point_0,point_1);
....

Events

The data and rendering of the image has been described briefly. The next important aspect of the button is input capturing and processing. The approach that was taken involves overriding methods from the base button class. These methods then directly change the state of the button via the properties. Once the state has been altered, they invalidate the control to let the OnPaint() mechanism refresh the image. Below is the list of event methods, and their purpose:

Event Methods Button state
OnMouseDown Set BtnState to Pushed and CapturingMouse to true
OnMouseUp Set BtnState to Normal and set CapturingMouse to false
OnMouseLeave Set BtnState to normal if we CapturingMouse = true
OnMouseMove If CapturingMouse = true and mouse coordinates are within button region, set BtnState to Pushed, otherwise set BtnState to Normal.

If CapturingMouse = false, then set BtnState to MouseOver

OnEnabledChanged The button either became enabled or disabled. If button became enabled, set BtnState to Normal else set BtnState to Inactive
OnLostFocus Set btnState to Normal

The below code block shows an example of the event code. Events generally are composed of little code. One tidbit of information I should cover is the Capture property of the control. By setting this to true, the button does not lose input focus when the pointer is outside the button region. This is important because if the mouse button is held down and the user moves the mouse pointer in and out of the button region, the state of the button needs to change accordingly. If the Capture property is not set, the control will stop capturing input events when the pointer leaves the button region.

/// <summary>
/// Mouse Down Event:
/// set BtnState to Pushed and Capturing mouse to true
/// </summary>
/// <param name="e"></param>
protected override void OnMouseDown(MouseEventArgs e)
{
  base.OnMouseDown (e);
  this.Capture = true;
  this.CapturingMouse = true;
  btnState = BtnState.Pushed;			
  this.Invalidate();
}

Wrapping Things Up...

This is the first incarnation of the control, and the entire source code is in one file, BitmapButton.cs. Later, if time permits and the control draws interest, the source code could make use of interfaces to componentize the different facets of functionality and abet expandability and maintainability. It would be nice to include themes and access them from an XML source. And caching (double buffering) of the images should be an option as well. I look forward to suggestions, and hope the bitmap control meets your needs or provides some ideas for your controls to be.

History

Version Date Changes
1.0 02-27-2005 Initial release of the control

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.

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