|

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 then 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 it implements the drop shadow functionality. This simply involves creating a 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 work around 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 Capturing mouse 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();
}
Rapping 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 |
| |
|
|
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 27 (Total in Forum: 27) (Refresh) | FirstPrevNext |
|
 |
|
|
I have a touch screen application.
There are a number of buttons (show menu) on this appliation which will show a menu. The menu is bascally a panel containinig a number of buttons. This panel is not visible until a mouse_down is issued on its show_menu button.
On mouse down the menu appears (panel.visible = true) . On mouse up this menu disappears (panel.visible = false). This menu consists of a number of buttons. These buttons can be highlighted by the user(changing the background image of the button) by dragging their finger over the buttons. If at any stage the user releases their finger (Mouse_Up) the menu disappears.
currently my way for calculating this is to get the mouse co-ordinates within the panel on the mouse move and comparing these co-ordinates to that of the o-ordinates of my buttons.
if ((e.X > -135) && (e.X < -29) && (e.Y > 47) && (e.Y > 74) { ' code change the background image to black for this button }
however I have 14 menus. Each menu having between 4 and 16 buttons each. Thats a lot of co-ordinates to check
Can this code be modified to allow it to work while issuing an Mouse_Down on another button.
Your code is calculating the mouse move over a button in its unpressed state, is there a way to convert your code below to detect the buttons within a panel,while the mouse_down is tied to a different control. Note, when mouse down is issued on the show_menu button, that button is covered by the panel being displayed until the mouse_up on that button is issued.
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (CapturingMouse) { System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, this.Width, this.Height); btnState = BtnState.Normal; if ((e.X >= rect.Left) && (e.X <= rect.Right)) { if ((e.Y >= rect.Top) && (e.Y <= rect.Bottom)) { btnState = BtnState.Pushed; } } this.Capture = true; this.Invalidate(); } else { //if(!this.Focused) { if (btnState != BtnState.MouseOver) { btnState = BtnState.MouseOver; this.Invalidate(); } } } }
Thank you
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Being a newbie to VS .NET2003 i wanted to know how to use your buttons in a wince form. Is there any method by which i can get your buttons in the toolbar so that i may use the graphical interface to drag and put them and alter any other properties?
Any help on this will be appreciated.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
I have found that sometimes a 4 black dots appear - basically 1 in each corner outside the CreateRegion area. If I resize the form, the black dots go away. If comment out the CreateRegion(0) method in the OnPaint routine, the problem goes away but I lost the rounded corners that I like very much. This is all in .NET 2.0 with VS2005. Have you seen this problem before?
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Does anyone know how to detect if a button is the default? Should I recursively find the parent until it is a form and the check the AcceptButton flag?
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
It’s nice control and I decide to use it. But I encounter some bugs. TreeView don't show resize cursor - there is black box instead splitter cursor and ComboBox not show its items. To avoid this it need to insert a GC.Collect() at the end of invoke chain of BitmapButton.OnPaint() and add invoke a dispose method for p0 and p1 in paint_ImageBorder in case of an ImageDropShadow.
|
| Sign In·View Thread·PermaLink | 1.00/5 (2 votes) |
|
|
|
 |
|
|
I got thousands of compile errors when I tried to use this class in my .NET CF 2.0 project...
|
| Sign In·View Thread·PermaLink | 1.67/5 (4 votes) |
|
|
|
 |
|
|
same here, could it be to with the mouse_over's etc? Is there any chance of a slimmed down CF friendly version coming soon?
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
Hello,
It's a very nice control, so I want to use it. I'm a beginner, and I don't understand all functions of owner drawn control. My application use multi languages and today I don't know if the size of my button is right. May be, in some languages, the text will be displayed on 2 lines. What is the way to display the text on severals lines ?
Thank you,
Freg.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Can the button be only the bitmap image without a border at all? How do I do this? I disabled the border, gave everything a transperent color and the button still has a border.
Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Even though the button control does not support a borderless option, it would not be difficult to append it. Create a property “DisableBorder”, and check for that property in the paint_Border() method. Happy Coding 
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
Hi! It'really a great job,but while I'm trying to use the control, sometimes, something wrong happens. I've added the control to the VS toolbox. I drag some bitmapbuttons into the form, but when I launch the debug process some buttons lose the text and the position (go to Location = 0,0). Any suggestion?
Thanks.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Manuelito:
I was working with this excelent control too, and I have a similar issue with the text locations when the button doesn´t have an image assigned and the ImageAlign property is set to MiddleRight. So, after debugging a while, I think I get the solution.
The problem is in the GetImageDestinationRect() function, when the image is null the function dont calculate the correct location and after that, the location of the TextRect is messed too. So, here is the fix that I applied. It tested and works fine now. 
/// /// Calculates the rectangular region used for image display. /// /// returns the rectangular region used to display the image private System.Drawing.Rectangle GetImageDestinationRect() { System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0,0,0,0); System.Drawing.Image image = GetCurrentImage(this.btnState); System.Drawing.Rectangle drect = new System.Drawing.Rectangle(0,0,this.Width,this.Height); drect.Inflate(-this.Padding,-this.Padding);
if(image!=null) { if(this.StretchImage) { rect.Width = this.Width; rect.Height= this.Height; } else { rect.Width = image.Width; rect.Height = image.Height; System.Drawing.Point pt = Calculate_LeftEdgeTopEdge(this.ImageAlign,drect, image.Width,image.Height); rect.Offset(pt); } } else { System.Drawing.Point pt = Calculate_LeftEdgeTopEdge(this.ImageAlign,drect, 0,0); rect.Offset(pt); }
return(rect); }
Regards
Sebastián Márquez Bs.As. Argentina
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Really nice button control! The is one bug though: If you set the buttons Enabled property to false from the Click event of the same button, it doesn't paint the way it should. How can I fix this?
Thanks for sharing this code!
Best regards, Balder
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I like the button you have made, however I really would like you to do a fix for the following behaviour:
When you set a quck key for the button via the ampersand character, your code does not replace it correctly with an underlined character, instead the ampersand is visible.
Perhaps MeasureCharacterRanges method of the Graphics object can get you started
DasCoder
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
There are two methods that need to be altered to fix the problem: txt_Size(), and paint_Text(). StringFormat needs to be included in all MeasureString() and DrawString() calls. Replace the original methods with the following code:
Thanks,
James
private System.Drawing.SizeF txt_Size(Graphics g,string strText,Font font) {
StringFormat sf = new StringFormat(); sf.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show;
System.Drawing.SizeF size = g.MeasureString(strText,font,9999,sf); return (size); }
private void paint_Text(PaintEventArgs e) { if(e == null) return; if(e.Graphics == null) return; StringFormat sf = new StringFormat(); sf.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show; System.Drawing.Rectangle rect = GetTextDestinationRect(); // // do offset if button is pushed // if( (btnState == BtnState.Pushed) && (OffsetPressedContent) ) rect.Offset(1,1); // // caculate bounding rectagle for the text // System.Drawing.SizeF size = txt_Size(e.Graphics,this.Text,this.Font); // // calculate the starting location to paint the text // System.Drawing.Point pt = Calculate_LeftEdgeTopEdge(this.TextAlign, rect, (int) size.Width, (int) size.Height); // // If button state is inactive, paint the inactive text // if(btnState == BtnState.Inactive) { e.Graphics.DrawString(this.Text,this.Font, new SolidBrush(System.Drawing.Color.White),pt.X+1,pt.Y+1,sf); e.Graphics.DrawString(this.Text,this.Font, new SolidBrush(System.Drawing.Color.FromArgb(50,50,50)),pt.X,pt.Y,sf); } // // else, paint the text and text shadow // else { // // 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,sf); e.Graphics.DrawString(this.Text,this.Font, TransparentBrush0,pt.X+1,pt.Y,sf); e.Graphics.DrawString(this.Text,this.Font, TransparentBrush1,pt.X+1,pt.Y+1,sf); e.Graphics.DrawString(this.Text,this.Font, TransparentBrush1,pt.X,pt.Y+2,sf); e.Graphics.DrawString(this.Text,this.Font, TransparentBrush1,pt.X+2,pt.Y,sf);
TransparentBrush0.Dispose(); TransparentBrush1.Dispose(); } // // paint text // e.Graphics.DrawString(this.Text,this.Font, new SolidBrush(this.ForeColor),pt.X,pt.Y,sf); } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
Well done on writing such a great control! I have found a very annoying bug tho where by if you disable the button in its on click handler, the button becomes disabled but gets stuck in a normal state! Is there anyway to quickly patch this code to make it draw itself disabled!? 
It appears to go wrong where the mouse is still over the button that you want to disable, since when I delay the disabling code it only disables the button image and text when the mouse is not still over the button..
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The below code will mask the error, since it will change the button state when disabled during the OnPaint event. When I rewrite the control, hopefully this problem will go away.
Thanks for pointing out the bug.
protected override void OnPaint(PaintEventArgs e) {
if(!this.Enabled) btnState = BtnState.Inactive;
CreateRegion(0); paint_Background(e); paint_Text(e); paint_Image(e); paint_Border(e); paint_InnerBorder(e); paint_FocusBorder(e); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
;PHave you thought about a BtnState that MouseOver and Inactive? You haven’t covered that. For example you have a list box where you hit a button to delete an item in list box and in case your list is empty and you want to make it Inactive while your mouse is still on that button. Your code fails that time. Even internally it is inactive your code put bitmap of active. You also need to change BtnState {Inactive = 1, Normal = 2 , MouseOver = 4, Pushed = 8}, so you could use bitwise & to find mutually inclusive states. The code has to be changed to reflect that changes. It is your code please change it.
Agha Khan
|
| Sign In·View Thread·PermaLink | 1.60/5 (5 votes) |
|
|
|
 |
|
|
Good job !!! Man! I'll add the beautiful button in my applications and test them in .net compact framework. Keep up the great work.
Wayne
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
| |