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:
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};
System.Drawing.Drawing2D.ColorBlend blend
= new System.Drawing.Drawing2D.ColorBlend();
blend.Colors = ColorArray;
blend.Positions = PositionArray;
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;
g.FillRectangle(brush, rect);
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.
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.
g.DrawImage(image,rect.Left,rect.Top)
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.
....
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);
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.
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.