Bug Fixes (05-Dec-2001)

CButtonSSL has now been used extensively in a real product and has such has
been through the mill a fair bit on the testing side. Thankfully this has given
me time to find and fix these problems, too.

This latest version includes fixes for a GDI resource leak (which I must
thank Dieter Hammer for finding), several changes to fix button state drawing
problems (which I must thank Eugene Pustovyt for pointing out some of the more
obscure ones) and also fixes a major bug with release builds.

The problem was that in a release build that included a CButtonSSL checkbox
whose state was altered using SetCheck the application would crash.
After enormous amounts of mostly fruitless debugging I eventually found a
way of reproducing the problem consistently, with the help of Alvaro Mendez.
Once I'd got to this stage I beginning to despait , but thankfully Joe Newcomer
came to my rescue and pointed out the error of my ways. The OnSetCheck handler
didn't define both of the required parameters for an ON_COMMAND handler. So, two
parameters pushed on the stack, only one popped off!

I'd like to thank everybody that has helped me out with this round of bug
fixes, hopefully there won't be (m)any more.

Update (18-Sep-2001)

It seems
that no matter how hard you try, you just can't get enough done in one day!
Trying to juggle work, play and family isn't easy! However, at last I have got
round to updating this article. So, what's new?

CButtonSSL now inherits from <a href="http://www.codeproject.com/buttonctrl/oddbutton.asp">COddButton</a>, though there are one or two
changes I've made to it (see below).

The Radio Button problem has been fixed (check out
the demo).

Several fixes have been implemented for drawing the
button.

Most of the interface to the menu has been exposed, so there's a whole raft
of new Menu Functions.

Because there's so much been added I haven't had chance to test it all extensively,
especially with respect to the menu functions. If there any problems, please
let me know (prefereably with a fix ;)).

Inheriting from COddButton

The change to inherit from COddButton has changed the handling of the standard
button styles. The use of COddButton::PreSubclassWindow means that the control
type is determined from the initial styles set in the dialog editor. So now
you cannot set the Owner Draw style, otherwise it won't work properly.

The handling of the default style is also provided entirely by COddButton now,
though as mentioned above, I have made one or two changes to handle radio buttons
as well.

The first change was to the constructor to make m_bCanBeDefault TRUE
initially. The second change was to the WM_GETDLGCODE handler,

Introduction

It seems that Buttons are the flavour of the month at the moment and here's
another. This button class is born out the need for a bitmap button with a menu
drop-down on it, but as usual ended up being so much more!

I found Norm Almond's "Cool Push Menu
Button" and Davide Calabro's CButtonST and
wanted to combine the two. Along the way I came across the
BS_OWNERDRAW problem and tried to find my own way around it.
Fortunately Paolo Messina and Jerzy Kaczorowski solved it for me with their COddButton.

The class I have come up with draws mainly from these sources, but also some
others and my thanks must go out to all those people who have helped me out in
the Visual C++ forum. It has taken me so long to complete this and I have been a
bit too relaxed with the documentation so if there is some code that you lay
claim to and want it recognised then please let me know and I'll update it.

The full documentation for the class follows. I hope this is sufficient because
it took me ages to write ;)

The CButtonSSL is a feature rich extension to the normal
CButton. It adds flat appearance, a menu drop-down, bitmap or icon
images, text and image alignment, control over the button colour, multiline
tooltip, correct default button handling, control over the cursor and the
ability to specifiy a URL to link to.

CButtonSSL provides owner-drawn functionality for check boxes,
radio buttons and push buttons. A CButtonSSL object can be created
for any of these.

A CButtonSSL object can have separate images for the button's
up, over, down and disabled states. If an image is not specified for the
disabled state a disabled images is created from the up image, which must always
be specified. Additionally, the button colour and text colour can be different
for the button's up, over and down states.

Creation of CButtonSSL object is the same as for a CButton
object. They may be created from a dialog template or directly in your code.
If they are created from a dialog template it is not necessary to call SubclassDlgItem
if the DDX_Control entry exists for the variable in the DoDataExchange
function of your dialog. However the button is created it must not have
the BS_OWNERDRAW style set.

Following construction the button object's appearance should be initialised.
The button's image(s) and the image alignment can be set using the Image
Functions. The button text font and alignment can be altered using the Text
Functions. The button's colours can be set using one of the Colour
Functions and the button cursor, tooltip or URL can be set using one of the
Miscellaneous
Functions. If the button object is a checkbox then the button's check state
can be set or retrieved using the Check
Box Functions.

Windows notification messages are sent by the button control to the parent
for CButtonSSL objects in the same manner as for
CButton objects.

If you create a CButtonSSL object from a dialog resource, the
CButtonSSL object is automatically destroyed when the user closes
the dialog box.

Remarks

Constructs a CButtonSSL object. The button style is initially
set to SSL_BS_FLAT, the image is initially aligned using
SSL_IMAGE_TOP and SSL_IMAGE_LEFT and the text is
initially aligned using SSL_TEXT_CENTER and
SSL_TEXT_VCENTER.

Return Value

Parameters

nResourceIDThe resource ID for the bitmap to be added.

crMaskThe colour to be used in the bitmap as a transparency
mask. This is <A href="#SSL_MASK">SSL_MASK</A>
by default.

Remarks

This function specifies a single bitmap to use for all states. The bitmap is
copied into an image list and then treated as an icon thereafter. When the
button is in the disabled state a disabled image is created from the specified
bitmap.

Return Value

Parameters

crUpMaskThe colour to be used in the up bitmap as a
transparency mask. This is <A href="#SSL_MASK">SSL_MASK</A>
by default.

nOverIDThe resource ID for the bitmap to be used for the
over state. This is zero by default.

crOverMaskThe colour to be used in the over bitmap as a
transparency mask. This is <A href="#SSL_MASK">SSL_MASK</A>
by default.

nDownIDThe resource ID for the bitmap to be used for the
down state. This is zero by default.

crDownMaskThe colour to be used in the down bitmap as a
transparency mask. This is <A href="#SSL_MASK">SSL_MASK</A>
by default.

nDisabledIDThe resource ID for the bitmap to be used for the
disabled state. This is zero by default.

crDisabledMaskThe colour to be used in the disabled bitmap
as a transparency mask. This is <A href="#SSL_MASK">SSL_MASK</A>
by default.

Remarks

The up bitmap must be defined, however, a resource ID of zero may be defined
for any state image that is not required. If no bitmap is specified for the over
state then the up image is used instead. If no bitmap is specified for the down
state then the over image is used instead (if this was not specified then the up
image will be used).

If a bitmap is specified for the disabled state then this will be used in
full colour. If it is not then the up image will be used to create a normal
disabled image.

Return Value

Parameters

nOverIDThe resource ID for the icon to be used for the over
state. This is zero by default.

nDownIDThe resource ID for the icon to be used for the down
state. This is zero by default.

nDisabledIDThe resource ID for the icon to be used for the
disabled state. This is zero by default.

Remarks

The up icon must be defined, however, a resource ID of zero may be defined
for any state image that is not required. If no icon is specified for the over
state then the up image is used instead. If no icon is specified for the down
state then the over image is used instead (if this was not specified then the up
image will be used).

If an icon is specified for the disabled state then this will be used in full
colour. If it is not then the up image will be used to create a normal disabled
image.

Return Value

Parameters

bUnderlineSpecifies whether the font is to be underlined. A
nonzero value sets the font to be underlined. A 0 value sets the font not to be
underlined. The font is not underlined by default.

bBoldSpecifies whether the font weight is to be bold or not.
A nonzero value sets the font to be bold. A 0 value sets the font weight to be
normal. The font weight is normal by default.

bStrikeOutSpecifies whether the font is to be struck out or
not. A nonzero value sets the font to be struck out. A 0 value sets the font not
to be struck out. The font is not struck out by default.

bItalicSpecifies whether the font style is to be italic or
not. A nonzero value sets the font style to be italic. A 0 value sets the font
style to be normal. The font style is normal by default.

fontA CFont object allowing full control over
the appearance of the font.

Remarks

The first version of this function provides simple control over the main
aspects of the button text font. If required the second version can be used to
provide full control over the appearance of the button text font.

Return Value

Parameters

nResourceIDSpecifies the menu ID of the menu resource to
load.

lpszResourceNamePoints to a null-terminated string that
contains the name of the menu resource to load.

Remarks

Both of these functions use the first submenu in the supplied menu, assuming
that it is a popup menu. For example, create a menu resource in the resource
editor. Set the first menu item to be pop-up. Add subitems below this to be
shown on the menu button.

The Windows message handling for the menu item selections must be handled by
the parent, there is no mechanism for CButtonSSL to handle
them.

Return Value

The previous state of the item: MF_CHECKED or MF_UNCHECKED, or
0xFFFFFFFF (MF_DOES_NOT_EXIST) if the menu item did not exist.

Parameters

nIDCheckItem
Specifies the menu item to be checked, as determined by nCheck.

nCheck
Specifies how to check the menu item and how to determine the item’s position
in the menu. The nCheck parameter can be a combination of MF_CHECKED
or MF_UNCHECKED with MF_BYPOSITION or MF_BYCOMMAND flags.
These flags can be combined by using the bitwise OR operator. They have the
following meanings:

MF_BYCOMMAND Specifies
that the parameter gives the command ID of the existing menu item. This is the
default.

MF_BYPOSITION Specifies
that the parameter gives the position of the existing menu item. The first
item is at position 0.

MF_CHECKED Acts as a toggle with MF_UNCHECKED to place the default
check mark next to the item.

MF_UNCHECKED Acts as a toggle with MF_CHECKED to remove a
check mark next to the item.

Remarks

Adds check marks to or removes check marks from menu items in the pop-up menu.
The nIDCheckItem parameter specifies the item to be modified.

The nIDCheckItem parameter may identify a pop-up menu item as well as a menu
item. No special steps are required to check a pop-up menu item. Top-level menu
items cannot be checked. A pop-up menu item must be checked by position since
it does not have a menu-item identifier associated with it.

nIDFirst
Specifies (as an ID or offset, depending on the value of nFlags) the first menu
item in the radio button group.

nIDLast
Specifies (as an ID or offset, depending on the value of nFlags) the last menu
item in the radio button group.

nIDItem
Specifies (as an ID or offset, depending on the value of nFlags) the item in
the group which will be checked with a radio button.

nFlags
Specifies interpretation of nIDFirst, nIDLast, and nIDItem in the following
way:

MF_BYCOMMAND Specifies that the parameter gives the command ID of
the existing menu item. This is the default if neither MF_BYCOMMAND
nor MF_BYPOSITION is set.

MF_BYPOSITION Specifies that the parameter gives the position of
the existing menu item. The first item is at position 0.

Remarks

Adds check marks to or removes check marks from menu items in the pop-up menu.
The nIDCheckItem parameter specifies the item to be modified.

The nIDCheckItem parameter may identify a pop-up menu item as well as a menu
item. No special steps are required to check a pop-up menu item. Top-level menu
items cannot be checked. A pop-up menu item must be checked by position since
it does not have a menu-item identifier associated with it.

Return Value

Previous state (MF_DISABLED, MF_ENABLED, or MF_GRAYED),
–1 if not valid or MF_DOES_NOT_EXIST if menu not initialised.

Parameters

nIDEnableItem
Specifies the menu item to be enabled, as determined by nEnable. This parameter
can specify pop-up menu items as well as standard menu items.

nEnable
Specifies the action to take. It can be a combination of MF_DISABLED,
MF_ENABLED, or MF_GRAYED, with MF_BYCOMMAND or MF_BYPOSITION.
These values can be combined by using the bitwise OR operator. These values
have the following meanings:

MF_BYCOMMAND Specifies
that the parameter gives the command ID of the existing menu item. This is the
default.

MF_BYPOSITION Specifies
that the parameter gives the position of the existing menu item. The first
item is at position 0.

MF_DISABLED Disables the
menu item so that it cannot be selected but does not dim it.

MF_ENABLED Enables the
menu item so that it can be selected and restores it from its dimmed state.

MF_GRAYED Disables the menu item so that it cannot be selected and
dims it.

Remarks

Enables, disables, or dims a menu item.

Using the MF_BYPOSITION value requires an application to use the correct
CMenu. If the CMenu of the menu bar is used, a top-level menu item (an item
in the menu bar) is affected. To set the state of an item in a pop-up or nested
pop-up menu by position, an application must specify the CMenu of the pop-up
menu.

When an application specifies the MF_BYCOMMAND flag, Windows checks
all pop-up menu items that are subordinate to the CMenu; therefore, unless duplicate
menu items are present, using the CMenu of the menu bar is sufficient.

Remarks

Return Value

The item ID for the specified item in a pop-up menu if the function is successful.
If the specified item is a pop-up menu (as opposed to an item within the pop-up
menu), the return value is –1. If nPos corresponds to a SEPARATOR
menu item, the return value is 0. If the menu is not yet initialised the value
MF_DOES_NOT_EXIST will be returned.

Parameters

nPos
Specifies the position (zero-based) of the menu item whose ID is being retrieved.

Remarks

Obtains the menu-item identifier for a menu item located at the position defined
by nPos.

Return Value

The value MF_DOES_NOT_EXIST if the specified item does not exist or
the menu is uninitialised. If nId identifies a pop-up menu, the high-order byte
contains the number of items in the pop-up menu and the low-order byte contains
the menu flags associated with the pop-up menu. Otherwise the return value is
a mask (Boolean OR) of the values from the following list (this mask describes
the status of the menu item that nId identifies):

MF_CHECKED Acts as a toggle with MF_UNCHECKED to place the default
check mark next to the item. When the application supplies check-mark bitmaps
(see the SetMenuItemBitmaps member function), the “check mark on” bitmap is
displayed.

MF_DISABLED Disables the
menu item so that it cannot be selected but does not dim it.

MF_ENABLED Enables the
menu item so that it can be selected and restores it from its dimmed state.
Note that the value of this constant is 0; an application should not test
against 0 for failure when using this value.

MF_GRAYED Disables the
menu item so that it cannot be selected and dims it.

MF_MENUBARBREAK Places
the item on a new line in static menus or in a new column in pop-up menus. The
new pop-up menu column will be separated from the old column by a vertical
dividing line.

MF_MENUBREAK Places the
item on a new line in static menus or in a new column in pop-up menus. No
dividing line is placed between the columns.

MF_SEPARATOR Draws a
horizontal dividing line. Can only be used in a pop-up menu. This line cannot
be dimmed, disabled, or highlighted. Other parameters are ignored.

MF_UNCHECKED Acts as a toggle with MF_CHECKED to remove a
check mark next to the item. When the application supplies check-mark bitmaps
(see the SetMenuItemBitmaps member function), the “check mark off”
bitmap is displayed. Note that the value of this constant is 0; an application
should not test against 0 for failure when using this value.

Parameters

nID
Specifies the menu item ID, as determined by nFlags.

nID
Specifies the nature of nID. It can be one of the following values:

MF_BYCOMMAND Specifies
that the parameter gives the command ID of the existing menu item. This is the
default.

MF_BYPOSITION Specifies that the parameter gives the position of
the existing menu item. The first item is at position 0.

Remarks

Returns the status of the specified menu item or the number of items in a pop-up
menu.

Return Value

If the function succeeds, the return value is nonzero. If the function fails,
the return value is zero. To get extended error information, use the Win32 function
GetLastError, as described in the Platform SDK.

Parameters

nIDItem
Identifier or position of the menu item to get information about. The meaning
of this parameter depends on the value of ByPos.

lpMenuItemInfo
A pointer to a MENUITEMINFO, as described in the Platform SDK, that contains
information about the menu.

rString
A reference to a CString object that is to receive the copied menu
string.

ByPos
Value specifying the meaning of nIDItem. By default, ByPos is FALSE, which indicates
that nIDItem is a menu item identifier. If ByPos is not set to FALSE, it indicates
a menu item position.

Remarks

This member function implements the behavior of the of the Win32 function GetMenuItemInfo,
as described in the Platform SDK.

Note that in the MFC implementation of GetMenuItemInfo, you do not use a handle
to a menu.

Return Value

Parameters

nPosition
Specifies the menu item to be changed. The nFlags parameter can be used to
interpret nPosition in the following ways:

MF_BYCOMMAND Specifies that the parameter gives the command ID of
the existing menu item. This is the default if neither MF_BYCOMMAND
nor MF_BYPOSITION is set.

MF_BYPOSITION Specifies that the parameter gives the position of
the existing menu item. The first item is at position 0.

nFlags
Specifies how nPosition is interpreted and gives information about
the changes to be made to the menu item.

nIDNewItem
Specifies either the command ID of the modified menu item or, if nFlags
is set to MF_POPUP, the menu handle (HMENU) of a pop-up
menu. The nIDNewItem parameter is ignored (not needed) if nFlags
is set to MF_SEPARATOR.

lpszNewItem
Specifies the content of the new menu item. The nFlags parameter can be used
to interpret lpszNewItem in the following ways:

MF_OWNERDRAW Contains an application-supplied 32-bit value that the
application can use to maintain additional data associated with the menu item.
This 32-bit value is available to the application when it processes MF_MEASUREITEM
and MF_DRAWITEM.

MF_STRING Contains a long pointer to a null-terminated string or
to a CString.

MF_SEPARATOR The lpszNewItem parameter is ignored (not needed).

pBmp
Points to a CBitmap object that will be used as the menu item.

Remarks

Changes an existing menu item at the position specified by nPosition. The application
specifies the new state of the menu item by setting values in nFlags. If this
function replaces a pop-up menu associated with the menu item, it destroys the
old pop-up menu and frees the memory used by the pop-up menu.

When nIDNewItem specifies a pop-up menu, it becomes part of the menu in which
it is inserted. If that menu is destroyed, the inserted menu will also be destroyed.
An inserted menu should be detached from a CMenu object to avoid conflict.

Return Value

Parameters

nPosition
Specifies the menu item to be changed. The nFlags parameter can
be used to interpret nPosition in the following ways:

MF_BYCOMMAND Specifies that the parameter gives the command ID of
the existing menu item. This is the default if neither MF_BYCOMMAND
nor MF_BYPOSITION is set.

MF_BYPOSITION Specifies that the parameter gives the position of
the existing menu item. The first item is at position 0.

nFlags
Specifies how nPosition is interpreted.

pBmpUnchecked
Specifies the bitmap to use for menu items that are not checked.

pBmpChecked
Specifies the bitmap to use for menu items that are checked.

Remarks

Associates the specified bitmaps with a menu item. Whether the menu item is
checked or unchecked, Windows displays the appropriate bitmap next to the menu
item.

If either pBmpUnchecked or pBmpChecked is NULL, then Windows displays nothing
next to the menu item for the corresponding attribute. If both parameters are
NULL, Windows uses the default check mark when the item is checked and removes
the check mark when the item is unchecked.

When the menu is destroyed, these bitmaps are not destroyed; the application
must destroy them.

The Windows GetMenuCheckMarkDimensions function retrieves the dimensions of
the default check mark used for menu items. The application uses these values
to determine the appropriate size for the bitmaps supplied with this function.
Get the size, create your bitmaps, then set them.

Return Value

Nonzero if successful; otherwise 0.

Parameters

nResourceIDSpecifies the cursor ID of the cursor resource to
load.

Remarks

The cursor specified will be shown when the mouse is over any part of the
button, including the drop-down arrow. The cursor will revert to it's previous
state when the drop-arrow is clicked for navigation of the menu.

Parameters

lpszTipTextPoints to a null-terminated string containing the
text to be displayed on the tooltip.

nResourceIDSpecifies the resource ID of a string in the
string table to be dispalyed on the tooltip.

bActivateSpecifies whether the tool tip is to be activated
or deactivated. The tool tip is activated by default.

Remarks

When the tool tip is active (bActivate = TRUE), the
tool tip information appears when the cursor hovered over the button; when it is
inactive (bActivate = FALSE), the tool tip information
does not appear, even when the cursor is hovered over the button.

Parameters

lpszURLPoints to a null-terminated string containing the URL
to be opened when the button is clicked.

Remarks

Call this function to define a URL to be opened when the
button is clicked. If a cursor has not already been defined for the button using
SetSSLButtonCursor then this function attempts to load
the hand cursor from WinHlp32.exe. This approach was detailed in Paul DiLascia's
Jan 1998 MSJ article. If the runtime environment does not have WinHlp32.exe in
the Windows directory then one can be defined and set using SetSSLButtonCursor.

Comments and Discussions

i not sure if there's any other found such problem:
I test buttonssl in a child dialog,and try to monitor the gdi objects of my app (record it when create/destroy) , then found that not all the increased gdi objects was release.

Hi, Derek, thanks for this button class. Though I’ve got one problem. In my project I would like to use menu drop-down button, everything works fine except this button intercepts occasionally the focus from other buttons. In other words – in my dialog I have several “regular” buttons and one menu drop-down CButtonSSL. If I click my “regular” button for the first time I can see that ButtonSSL gets the focus, and function attributed to this “regular” button is not called. So I have to click this “regular” button for the second time. Only then its handler been called. So all the “regular” buttons have to be clicked twice.

Thanks again for you class and I will appreciate any of your thoughts regarding the problem I’ve described before.

The compiler has reached the end of the file, and failed to find a precompiled header directive ?

The FAQ answers this question, your project is set to use a precompiled header ( probably stdafx.h ), but the file does not include it. You should include it before all other headers, or change the setting to the more logical default, which is to automatically use precompiled headers, so it does not use them when they are not included.

Christian

No offense, but I don't really want to encourage the creation of another VB developer. - Larry Antram 22 Oct 2002

In the current version of CButtonSSL, if the left side is checked, the right is shown checked as well. You should have a way to leave the right side in the up position unless the menu is open. How readily can this be done?

Will, I believe if you look at the 'standard' MS buttons that have a drop-down menu, the left and right side is drawn in the down state when the button portion is clicked. Hence the behaviour seen in CButtonSSL.

If you prefer to have different behaviour then by all means modify the code to get what you want. All the state data for the button and for the menu are in the class, you just need to trap it at the right point and draw the parts of the button separately.

Derek Lakin.I wish I was what I thought I was when I wished I was what I am.

First of all, this is a nice control.
But when you want to set an icon within the button by calling SetSSLButtonIcon
there is a call to load icon but that icon is never destroyed by a call to DestoryIcon. But a copy is added (en used futher ) in the imagelist.

I have downloaded the latest version of your button control. I tested it on a machine running Windows 95. Still there are resource which get lost. Afterwards i tryed to simultate this behaviour by creating a test application the result was when using Win95 deleting a GDI object while it's still is selected in a device context causes the resource isn't released.
foe example in youre DrawItem function there is some fragment you say:
CPen brLite (PS_SOLID, 1, ::GetSysColor (COLOR_BTNHIGHLIGHT));
pDC->SelectObject (&brLite);
pDC->MoveTo (rectSplit.right + 1 , rectSplit.top);
pDC->LineTo (rectSplit.right + 1, rectSplit.bottom);
brLite.DeleteObject();

After DeleteObject the pen isn't released only under Win95 when i used the same under win2000 there is no problem as far as i can see.

If you whish to receive this test application and tool please let me know.

I've been looking at the documentation and it says the following:
The following four steps are typically used when you need a graphic object for a drawing operation:

Define a graphic object on the stack frame. Initialize the object with the type-specific create function, such as CreatePen. Alternatively, initialize the object in the constructor. See the discussion of one-stage and two-stage creation, which provides example code.

Select the object into the current device context, saving the old graphic object that was selected before.

When done with the current graphic object, select the old graphic object back into the device context to restore its state.

Allow the frame-allocated graphic object to be deleted automatically when the scope is exited.

So it would seem in the case you have highlighted that the correct code would be as follows:

In this way the pen is correctly restored to the device context and then when the CPen objects go out of scope they will be correctly destroyed. However, the documentation does also say that you shouldn't call DeleteObject on a CGDIObject that is still selected in the device context which is what is happening in your example. Perhaps this is the problem?

Greetings!!!
The entire CButtonSSL class has been stolen by someone who wrote his article at: http://www.mindcracker.com/code/CustBtnInVCNetJM.asp
Just scroll down the above written web-page and You will realize that.