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.

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.

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.

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.

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.

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.

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

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.

Share

About the Author

I've enjoyed programming for over 15 years, beginning with the commodore 64. That was an interesting time, since creating a program involved using a tool named HESMON to write hex OPCODEs directly into memory. That was the days of PEEKS, POKES, and SYS. From there, I moved to the Amiga, and eventually to the 8088. Perhaps, I should say ‘We’, since my brother and I share the same enthusiasm and worked together for a number years.

As with many others, my initial interests in programming was sparked by games. However, as time progressed, it was more lucrative to put gaming on the shelf, and move into the business world. This has led me to work with CGI, Javascript, Asp, and now the Dot Net technologies. I’ve been fortunate to work with great people creating CE, Win32 and Web applications. With that said, many of the techniques learned early on in gaming can still be useful and applied even today.

If you wish to contact me please send an e-mail to: jsimms_articles@yahoo.com

thanks so much for your most excellent project. extremely well written presentation, as well as very clean and well organized code.

i would like to use this in a custom overridden messagebox class, but would rather not set a reference to your compiled dll. i'd prefer simply embedding your class in the my messagebox class, and call it from there at run time.

how do i get the buttons to display on my messagebox form without dragging them from the toolbox?

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.

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.

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?

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?

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.

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 ?

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

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?

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.

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: