Topic : Buttons
Author : Unknown
Page : << Previous 3  Next >>
Go to page :


different. When one radio button is checked, the other button should be unchecked. So within both CBtnDlg::OnBmpRadioA() and CBtnDlg::OnBmpRadioB(), function CBtnDlg:: SetRadioBitmap() is called to set bitmaps for both radio buttons:

(Code omitted)

When the user clicks one of the radio buttons, its resource ID is assigned to variable CBtnDlg::m_uBmpRadio. Then in function CBtnDlg::SetRadioBitmap(), this variable is compared to both radio button IDs. Bitmap IDB_BITMAP_BTNCHECK will be associated to the button whose ID is currently stored in variable CBtnDlg::m_uBmpRadio. For the other button, bitmap IDB_BITMAP_BTNUNCHECK will be loaded.

The appearance of our new check box and radio buttons is almost the same with that of old ones implemented in sample 2\Btn. However, here the rectangle with dashed border will not be put over a button's face when the button has the current focus (Figure 4-6).

(Figure omitted)

5 Irregular Shape Bitmap Button

Transparent Background

Up to now all the buttons created by us have a rectangular shape. It is relatively difficult to create a button with irregular shapes (e.g., a round button). Even for bitmap buttons, their associated bitmaps are always rectangular.

We may think that by setting the bitmap's background color to the color of the dialog box, the bitmap button may look like a non-rectangular button. For example, in the previous samples, button IDC_PLAY is made up of two regions: its foreground is the triangular region, and rest area can be treated as its background (Figure 4-7). We can change the background color to the color of the dialog box so that this area appears transparent to us when the bitmap button is implemented.

However, this is not the best solution. In Windows? operating system, the color of dialog box can be customized. The user can double click "Display" icon contained in the "Control Panel" window and set the colors for many objects, which include title bar, backdrop..., and so on. So actually we have no way of knowing the color of dialog box beforehand. If we implement a bitmap button and set its background color to the dialog box color in our system, it may not work properly in another system.

To draw a bitmap with transparent background, we need to use "mask" when painting the bitmap. We can imagine a mask as a matrix with the same dimension of the bitmap image, and each element in the matrix corresponds to a pixel contained in the bitmap. The elements in the matrix may be either "0" or "1". When we paint the bitmap, only those pixels with "0" masks are output to the device (we can also use "1" to indicate the pixels that need to be drawn).

When programming the application, we can prepare two bitmaps of exactly the same size: one stores the normal image, the other one stores mask. The mask bitmap is made up of only two types of pixels: black and white. This is because for black color, its RGB elements are all 0s; for white color, the elements are all 1s. By implementing the mask bitmap like this, the background area of the mask bitmap is white and the foreground area is black.

Windows allows us to output the pixels of a bitmap to the target device using different operation mode. We can copy a pixel directly to the target device, we can also combine a pixel contained in the bitmap with the pixel contained in the target device using various mode: bit-wise OR, AND, and XOR. For example, if we combine red color (RGB(255, 0, 0)) with green color (RGB(0, 255, 0)) using bit-wise AND operation, the output will be yellow color (RGB(255, 255, 0)).

When painting the bitmap, we first need to output the normal bitmap to the target device using bit-wise XOR drawing mode, then output the mask bitmap to the target device at the same position using bit-wise AND mode. Finally we can output the normal bitmap again using bit-wise XOR mode. By doing this, the output bitmap's background will become transparent.

The reason for this is simple. Before bitmap is painted, the target device may contain a uniform color or any image pattern. After normal bitmap is first painted, the foreground area of the target device will become the combination of source image pattern and target image pattern. After we output mask bitmap using bit-wise AND operation, the foreground area of the target device will become black. This means on the target device, every pixel in the foreground area is zero now. Since we use bit-wise XOR mode to output normal image in the last step, and XORing anything with zero will not change the source, the foreground area on the target device will finally contain the pattern of the source image. For mask area, the second ANDing operation doesn't make any change to it because ANDing a pixel with white color (all 1s) doesn't change that pixel. So the overall operations on the mask region is equivalent to two consecutive XOR operations, which will resume all the pixels in this region to their original colors.

However there is still a slight problem here: if we draw the source and mask bitmaps directly to the target device using the method mentioned above, we will see a quick flicker on the mask area of the target device. Although it lasts only for a very short period, it is an undesirable effect. To overcome this, we can prepare a bitmap in the memory, copy the target image pattern to this bitmap, do the transparent painting on the memory bitmap, and copy the final result back to the target. Since the background area of the memory bitmap has the same pattern with the background area of the target device, this final copy will not cause any flicker.

To customize the drawing behavior of bitmap button, we need to override function CBitmapButton::OnDrawItem(...). For an owner-draw button, this function will be called when a button needs to be updated. Actually, menu and combo box also use similar functions. We can create an owner draw menu or combo box by overriding the member functions with the same name. For these functions, their parameters are all pointers to DRAWITEMSTRUCT type object. This structure stores information such as button's current state (i.e. focused, disabled), the device context that can be used to implement drawing, and the rectangle indicating where we can output the image pattern.

New Class

Sample 5\Btn is based on sample 4\Btn, it demonstrates how to create bitmap buttons with transparent background. In this sample, a new class MCBitmapButton is derived from CBitmapButton. Besides the default properties inherited from base class, the following new features are added to this class:

1) The new class handles transparent background drawing automatically. Programmer can prepare a black-and-white mask bitmap and associate it with the bitmap button together with other required bitmaps. The drawing will be taken care in the member function of the class. Programmer doesn't need to add extra code.

2) The mask bitmap can be loaded along with other images by calling either AutoLoad(...) or LoadBitmaps(...).

3) Function AutoLoad(...) is overridden to load the mask image automatically. In this case, the mask bitmap must have a string ID that is created by suffixing an 'M' character to button's caption text. For example, if we want to create mask bitmap for a button whose caption text is "PLAY", the ID of the mask bitmap must be "PLAYM". If we load the mask bitmap using automatic method, there is no difference between using the new class and CBitmapButton.

4) Mask bitmap could also be loaded by calling function LoadBitmaps(...). The overridden function has five parameters, the last of which is the ID of the mask bitmap.

5) If the mask bitmap is not present, the bitmap will be output directly to the target device using the default implementation.

In the sample, a mask bitmap "PLAYM" is added to the application. It will be used to draw button IDC_PLAY with transparent background.

The new class derived from CBitmapButton is MCBitmapButton. In this class, a new CBitmap type variable m_bitmapMask is added to load the mask bitmap, also, functions AutoLoad(...) and LoadBitmaps(...) are overridden. The following code fragment shows this new class:

(Code omitted)

Function LoadBitmaps(...) has two versions, one is used to load bitmaps with string IDs, the other is used to load bitmaps with integer IDs. Both functions have five parameters.

Overriding Function CBitmapButton::LoadBitmaps(...)

When overriding functions, we can utilize the features implemented by the base class to load standard four bitmaps, and add our own code to load the mask bitmap. The following is the implementation of function MCBitmapButton::LoadBitmaps(...):

(Code omitted)

First we must use variable m_bitmapMask to call function CBitmap::DeleteObject(), which is inherited from class CGdiObject. Since bitmap is a GDI (graphics device interface) object, once it is initialized, it will allocate some memory. If we want to initialize it again, we must first release the previously allocated memory. Function CGdiObject::DeleteObject() can be used for this purpose.

Next we call function CBitmapButton::LoadBitmaps(...) to load the default four bitmaps, and see if the mask bitmap is available. If so, we use m_bitmapMask to load the mask bitmap.

Overriding Function CBitmapButton::AutoLoad(...)

In member function MCBitmapButton::AutoLoad(...), the bitmap resource IDs

Page : << Previous 3  Next >>