You can use a brush to paint the interior of virtually any object that you draw with a GDI function. This includes the interior of characters in a string of text, the interior of rectangles, ellipses, polygons, and the interior of paths. Depending on the requirements of your application, you can use a solid brush of a specified color, one of the seven stock brushes, a hatch brush, or a custom brush.
This section contains sample code that demonstrates the creation of a Custom Brush dialog box. The dialog box contains a grid that represents the 8-by-8 pixel bitmap which Windows uses as a brush. Using this grid, the user can create a custom-brush bitmap. And, once a bitmap is created, the user can view the resultant custom brush by clicking the Test Pattern button.
The following illustrations show several sample patterns created using this dialog:
Custom Brush Dialog: First Sample Pattern
Custom Brush Dialog: Second Sample Pattern
Custom Brush Dialog: Third Sample Pattern
In order to display a dialog box, it's first necessary to create a dialog box template. The template for the Custom Brush dialog is shown below:
#include "windows.h"
#include "generic.h"
GenericMenu MENU
BEGIN
POPUP "Art"
BEGIN
MENUITEM "Stock Brush", IDM_STOCKBRUSH
MENUITEM "Hatch Brush", IDM_HATCHBRUSH
MENUITEM "Pattern Brush", IDM_PATTERNBRUSH
MENUITEM "Erase the Screen", IDM_ERASE_CLIENT
MENUITEM "Draw Star", IDM_STAR
MENUITEM "Draw Circles", IDM_CIRCLE
MENUITEM "Custom Brush", IDM_CUSTOMBRUSH
END
END
STRINGTABLE
BEGIN
IDS_FILTERSTRING "EnhMetaFiles(*.EMF)%*.EMF%"
IDS_DEFEXTSTRING "EMF"
IDS_TITLESTRING "Create MetaFile"
IDS_DESCRIPTIONSTRING "SDK Sample%Cross-Section View%"
END
CustBrush DIALOG 6, 18, 160, 118
STYLE WS_DLGFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION
CAPTION "Custom Brush"
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "", IDD_GRID, "Static", SS_BLACKFRAME | WS_CHILD, 3, 2, 83, 79
CONTROL "", IDD_RECT, "Static", SS_BLACKFRAME | WS_CHILD, 96, 11, 57, 28
PUSHBUTTON "Paint Rectangle", IDD_PAINTRECT, 96, 47, 57, 14
PUSHBUTTON "OK", IDD_OK, 29, 98, 40, 14
PUSHBUTTON "Cancel", IDD_CANCEL, 92, 98, 40, 14
END
The Custom Brush dialog-box contains five controls: the bitmap-grid window, the pattern-viewing window, a pushbutton that allows the user to view the pattern, an OK pushbutton, and a Cancel pushbutton. The dialog template assigns a value to each of these controls, specifies the location of each control, specifies the dimensions of the dialog window, and so on. (For more information about the creation of dialog templates, see Chapter 49, “Dialog Boxes.”)
In the dialog template, the control values are actually constants which have been defined in the application's header file. The following excerpt from the header file shows how these constants were defined:
#define IDD_GRID 120
#define IDD_RECT 121
#define IDD_PAINTRECT 122
#define IDD_OK 123
#define IDD_CANCEL 124
Once the dialog template has been created and included in the application's resource file, a dialog procedure should be written. The dialog procedure processes messages which Windows sends to the dialog box. The following excerpt from an application's source file shows the dialog procedure for the Custom Brush dialog box (as well as two functions which are called by this procedure):
int APIENTRY BrushDlgProc(HWND hdlg, WORD message, LONG wParam, LONG lParam)
{
static HWND hwndGrid; /* grid-window control */
static HWND hwndBrush; /* sample-brush control */
static RECT rctGrid; /* grid-window rectangle */
static RECT rctBrush; /* sample-brush rectangle */
static UINT bBrushBits[8];/* bitmap bits */
static RECT rect[64];/* grid-cell array */
static HBITMAP hbm; /* bitmap handle */
HBRUSH hbrush;/* current brush */
HBRUSH hbrushOld;/* default brush */
HRGN hrgnCell;/* test-region handle */
HDC hdc; /* dc handle */
int x, y, deltaX, deltaY; /* drawing coordinates */
POINTS ptlHit;/* mouse coordinates */
int i;/* count variable */
switch (message)
{
case WM_INITDIALOG:
/* Retrieve a window handle for the grid and brush controls.*/
hwndGrid = GetDlgItem(hdlg, IDD_GRID);
hwndBrush = GetDlgItem(hdlg, IDD_RECT);
/* Initialize the array of bits which define the custom */
/* brush with the value 1 (resulting in a white brush). */
for (i=0; i<8; i++)
bBrushBits[i] = 0xFF;
/* Retrieve the grid- and brush-control dimensions. */
GetClientRect(hwndGrid, &rctGrid);
GetClientRect(hwndBrush, &rctBrush);
/* Determine the width and height of a single cell. */
deltaX = (rctGrid.right - rctGrid.left)/8;
deltaY = (rctGrid.bottom - rctGrid.top)/8;
/* Initialize the array of cell rectangles. */
for (y=rctGrid.top, i=0; y < rctGrid.bottom; y += deltaY){
for (x=rctGrid.left; x < (rctGrid.right - 8) && i < 64;
x += deltaX, i++) {
rect[i].left = x; rect[i].top = y;
rect[i].right = x + deltaX; rect[i].bottom = y + deltaY;
}
}
return FALSE;
case WM_PAINT:
/* Draw the grid. */
hdc = GetDC(hwndGrid);
for (i=rctGrid.left; i<rctGrid.right;
i+=(rctGrid.right - rctGrid.left)/8){
MoveToEx(hdc, i, rctGrid.top, NULL);
LineTo(hdc, i, rctGrid.bottom);
}
for (i=rctGrid.top; i<rctGrid.bottom;
i+=(rctGrid.bottom - rctGrid.top)/8){
MoveToEx(hdc, rctGrid.left, i, NULL);
LineTo(hdc, rctGrid.right, i);
}
ReleaseDC(hwndGrid, hdc);
return FALSE;
case WM_LBUTTONDOWN:
/* Store the mouse coords in a POINT structure. */
ptlHit = MAKEPOINTS((POINTS FAR *)lParam);
/* Create a rectangular region with dimensions and*/
/* coordinates that correspond to those of the grid */
/* control window.*/
hrgnCell = CreateRectRgn(rctGrid.left, rctGrid.top,
rctGrid.right, rctGrid.bottom);
/* Retrieve a window-DC for the grid window. */
hdc = GetDC(hwndGrid);
/* Select the region into the DC. */
SelectObject(hdc, hrgnCell);
/* Test for a button-click in the grid window. */
if (PtInRegion(hrgnCell, ptlHit.x, ptlHit.y)){
/* A button-hit occurred in the grid window, now */
/* isolate the cell in which it occurred. */
for(i=0; i<64; i++){
DeleteObject(hrgnCell);
hrgnCell = CreateRectRgn(rect[i].left, rect[i].top,
rect[i].right, rect[i].bottom);
if (PtInRegion(hrgnCell, ptlHit.x, ptlHit.y)){
InvertRgn(hdc, hrgnCell);
/* Set the appropriate brush bits. */
if (i % 8 == 0)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x80;
else if (i % 8 == 1)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x40;
else if (i % 8 == 2)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x20;
else if (i % 8 == 3)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x10;
else if (i % 8 == 4)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x08;
else if (i % 8 == 5)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x04;
else if (i % 8 == 6)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x02;
else if (i % 8 == 7)
bBrushBits[i/8] = bBrushBits[i/8] ^ 0x01;
/* Exit the "for" loop once the bit is set. */
break;
} /* end if */
} /* end for */
} /* end if */
/* Release the DC for the control. */
ReleaseDC(hwndGrid, hdc);
return TRUE;
case WM_COMMAND:
switch (wParam){
case IDD_PAINTRECT:
hdc = GetDC(hwndBrush);
/* Create a monchrome bitmap. */
hbm = CreateBitmap(8, 8, 1, 1, (LPBYTE)bBrushBits);
/* Select the custom brush into the dc. */
hbrush = CreatePatternBrush(hbm);
hbrushOld = SelectObject(hdc, hbrush);
/* Fill the rectangle using the brush. */
Rectangle(hdc, rctBrush.left, rctBrush.top,
rctBrush.right, rctBrush.bottom);
/* Clean-up memory. */
SelectObject(hdc, hbrushOld);
DeleteObject(hbrush);
DeleteObject(hbm);
ReleaseDC(hwndBrush, hdc);
return TRUE;
case IDD_OK:
case IDD_CANCEL:
EndDialog(hdlg, TRUE);
return TRUE;
} /* end switch */
break;
default:
return FALSE;
}
}
int GetStrLngth(LPTSTR cArray)
{
int i = 0;
while (cArray[i++] != 0);
return (i-1);
}
DWORD RetrieveWidth(LPTSTR cArray, int iLength)
{
int i, iTmp;
double dVal, dCount;
dVal = 0.0;
dCount = (double)(iLength-1);
for (i=0; i<iLength; i++){
iTmp = cArray[i] - 0x30;
dVal = dVal + (((double)iTmp) * pow(10.0, dCount--));
}
return ((DWORD)dVal);
}
The dialog procedure for the Custom Brush dialog-box processes four messages. The following list names each message and briefly describes how the dialog procedure processes each one:
Message Name | Related Processing |
WM_INITDIALOG | The dialog procedure retrieves a window handle for the grid and test-pattern controls. It retrieves the dimensions of each of these controls. It computes the dimensions of a single cell in the grid control. And it initializes an array of grid-cell coordinates. |
WM_PAINT | The dialog procedure draws the grid pattern in the grid control. |
WM_LBUTTONDOWN | The dialog procedure determines whether the cursor was within the grid control when the user pressed the left mouse button. If it was, the dialog procedure inverts the appropriate grid cell and records the state of that cell in an array of bits that will be used to create the bitmap for the custom brush. |
WM_COMMAND | The dialog procedure processes input for the three pushbutton controls. If the user presses the Test Pattern pushbutton, the dialog procedure paints the test-pattern control with the new custom brush. If the user presses the OK or Cancel pushbutton, the dialog procedure performs appropriate actions. |
Once the dialog procedure has been written, several final steps remain: exporting the procedure in the application's module definition file, including the function definition for the procedure in the application's header file, and calling the dialog procedure at the appropriate point in the application.
The following excerpt from the application's module definition file shows how the dialog procedure was exported:
; All functions that will be called by any Windows routine
; MUST be exported.
EXPORTS
MainWndProc @1 ; name of window processing function
BrushDlgProc @2 ; name of custom-brush processing function
The following excerpt from the application's header file shows the function definition for the dialog procedure and the two functions which are called, in turn, by this procedure:
int APIENTRY BrushDlgProc(HWND hdlg, WORD message, LONG wParam, LONG lParam);
int GetStrLngth(LPTSTR cArray);
DWORD RetrieveWidth(LPTSTR cArray, int iLength);
Finally, the last step involves calling the dialog procedure from the application's source file. In this example, the dialog procedure was called when the user chose an option in the application's menu. The following excerpt from the application's source file shows how the dialog procedure was called:
switch (message) {
case WM_CREATE:
break;
case WM_COMMAND: /* message: command from application menu */
switch(wParam) {
case IDM_CUSTOMBRUSH:
DialogBox((HANDLE)GetModuleHandle(NULL), (LPTSTR)"CustBrush",
hWnd, (DLGPROC)BrushDlgProc);
break;