Each shape will know how to draw itself correctly based on its concrete dynamic type that is, if it is a LineShape then it will know to draw a line, if it is a RectShape then it will kno
Trang 1Next, we create a global vector of Shape pointers, gShapes, which will maintain all the shapes we have created so that we can draw all the shapes whenever a WM_PAINT message is generated In addition, we keep a global shape pointer gShape, which will be the temporary shape we will draw while the user is moving the mouse around and deciding where exactly to make the shape permanent Notice how we can talk about shapes in general This is because of the polymorphism Each shape will know how to draw itself correctly based on its concrete dynamic type (that is, if it is a LineShape then it will know to draw a line, if it is a RectShape then it will know to draw a rectangle, and so on)
Figure 15.17: User can select which type of shape to draw via the menu
We need to keep track of which primitive menu item is selected so that we know which kind of shape to create To facilitate this, we keep a global variable that keeps track of the currently selected primitive:
int gCurrPrimSel = ID_PRIMITIVE_LINE;
Later in the WM_LBUTTONDOWN message handler, we create the shape based on the currently selected type:
Trang 2int gCurrPenColSel = ID_PENCOLOR_BLACK;
int gCurrBrushColSel = ID_BRUSHCOLOR_BLACK;
int gCurrPenStyleSel = ID_PENSTYLE_SOLID;
int gCurrBrushStyleSel = ID_BRUSHSTYLE_SOLID;
The values to which these global variables are initialized are the program’s “default” selected values Therefore, we also need to check the corresponding menu items at the start of the program so that the default selections are checked We implement this functionality in the WM_CREATE message:
case WM_CREATE:
CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED);
CheckMenuItem(ghMenu, ID_PENCOLOR_BLACK, MF_CHECKED);
CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLACK, MF_CHECKED);
CheckMenuItem(ghMenu, ID_PENSTYLE_SOLID, MF_CHECKED);
CheckMenuItem(ghMenu, ID_BRUSHSTYLE_SOLID, MF_CHECKED);
return 0;
This way, the default-selected menu items will be checked when the window is first created
We also keep a global instance of a LOGPEN and LOGBRUSH:
CheckMenuItem(ghMenu, ID_PENSTYLE_DOTTED, MF_CHECKED);
CheckMenuItem(ghMenu, gCurrPenStyleSel, MF_UNCHECKED);
When a shape is created, we pass gLogPen and gLogBrush into the constructor For example,
gShape = new LineShape(p0, p1, gLogPen, gLogBrush);
Because we update gLogPen and gLogBrush immediately as the user selects new colors and styles from the menu, these objects always reflect the current user menu selections Therefore, any object created will always be created with the currently selected pen and brush colors, and pen and brush styles, which is the exact functionality which should happen
The majority of code in the paint-drawing program has to do with the menu and updating the primitive type that is selected, the colors of the pen and brush, and the styles of the pen and brush The code is
Trang 3long, but simple For example, the part of the code that checks for which primitive type the user selected is as follows:
CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED);
CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED);
gCurrPrimSel = ID_PRIMITIVE_LINE;
return 0;
case ID_PRIMITIVE_RECTANGLE:
CheckMenuItem(ghMenu, ID_PRIMITIVE_RECTANGLE, MF_CHECKED);
CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED);
gCurrPrimSel = ID_PRIMITIVE_RECTANGLE;
return 0;
case ID_PRIMITIVE_ELLIPSE:
CheckMenuItem(ghMenu, ID_PRIMITIVE_ELLIPSE, MF_CHECKED);
CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED);
gCurrPrimSel = ID_PRIMITIVE_ELLIPSE;
return 0;
It is all quite redundant We simply update which menu items should be checked, and update the global
gCurrPrimSel variable so that it reflects the currently selected item We execute similar code for the pen color menu items, the brush color menu items, the pen style menu items, and the brush style menu items As mentioned, the code is all largely the same, so we will not discuss it further
You should now be able to understand the drawing program The code is listed in Program 15.5
Program 15.5: Paint drawing program
const COLORREF BLACK = RGB(0, 0, 0);
const COLORREF WHITE = RGB(255, 255, 255);
const COLORREF RED = RGB(255, 0, 0);
const COLORREF GREEN = RGB(0, 255, 0);
const COLORREF BLUE = RGB(0, 0, 255);
HWND ghMainWnd = 0;
HINSTANCE ghAppInst = 0;
HMENU ghMenu = 0;
Trang 4vector<Shape*> gShapes;
Shape* gShape = 0;
bool gMouseDown = false;
int gCurrPrimSel = ID_PRIMITIVE_LINE;
int gCurrPenColSel = ID_PENCOLOR_BLACK;
int gCurrBrushColSel = ID_BRUSHCOLOR_BLACK;
int gCurrPenStyleSel = ID_PENSTYLE_SOLID;
int gCurrBrushStyleSel = ID_BRUSHSTYLE_SOLID;
CheckMenuItem(ghMenu, ID_PRIMITIVE_LINE, MF_CHECKED);
CheckMenuItem(ghMenu, ID_PENCOLOR_BLACK, MF_CHECKED);
CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, ID_PENSTYLE_SOLID, MF_CHECKED);
CheckMenuItem(ghMenu, ID_BRUSHSTYLE_SOLID, MF_CHECKED);
return 0;
case WM_COMMAND:
switch( LOWORD(wParam) ) {
//=======================================
// File Menu //=======================================
return 0;
case ID_PRIMITIVE_RECTANGLE:
Trang 5CheckMenuItem(ghMenu, ID_PRIMITIVE_RECTANGLE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_RECTANGLE;
return 0;
case ID_PRIMITIVE_ELLIPSE:
CheckMenuItem(ghMenu, ID_PRIMITIVE_ELLIPSE, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPrimSel, MF_UNCHECKED); gCurrPrimSel = ID_PRIMITIVE_ELLIPSE;
return 0;
//=======================================
// Pen Colors //=======================================
case ID_PENCOLOR_BLACK:
CheckMenuItem(ghMenu, ID_PENCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, gCurrPenColSel, MF_UNCHECKED); gCurrPenColSel = ID_PENCOLOR_BLACK;
return 0;
//=======================================
// Brush Colors //=======================================
case ID_BRUSHCOLOR_BLACK:
CheckMenuItem(ghMenu, ID_BRUSHCOLOR_BLACK, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_BLACK;
return 0;
Trang 6case ID_BRUSHCOLOR_RED:
CheckMenuItem(ghMenu, ID_BRUSHCOLOR_RED, MF_CHECKED);
CheckMenuItem(ghMenu, gCurrBrushColSel, MF_UNCHECKED); gCurrBrushColSel = ID_BRUSHCOLOR_RED;
return 0;
//=======================================
// Pen Styles //=======================================
case ID_PENSTYLE_SOLID:
CheckMenuItem(ghMenu, ID_PENSTYLE_SOLID, MF_CHECKED);
CheckMenuItem(ghMenu, gCurrPenStyleSel, MF_UNCHECKED); gCurrPenStyleSel = ID_PENSTYLE_SOLID;
gLogPen.lopnStyle = PS_DASH;
return 0;
//=======================================
// Brush Styles //=======================================
case ID_BRUSHSTYLE_SOLID:
CheckMenuItem(ghMenu, ID_BRUSHSTYLE_SOLID, MF_CHECKED); CheckMenuItem(ghMenu, gCurrBrushStyleSel, MF_UNCHECKED); gCurrBrushStyleSel = ID_BRUSHSTYLE_SOLID;
Trang 7gCurrBrushStyleSel = ID_BRUSHSTYLE_DIAGONAL; gLogBrush.lbStyle = BS_HATCHED;
// Create the shape based on what shape the user has
// selected in the menu
switch( gCurrPrimSel ) {
// Current mouse position is stored in the lParam p1.x = LOWORD(lParam);
p1.y = HIWORD(lParam);
// Update the end point of the current temporary
// shape based on the mouse position
gShape->setEndPt(p1);
// Repaint the window so the temporary shape
Trang 8// is redrawn interactively as the mouse moves InvalidateRect(hWnd, 0, true);
} return 0;
// Update the end point of the current temporary shape
// based on the mouse position
gShape->setEndPt(p1);
// The user lifted the left mouse button, so the shape // becomes permanent, so add it to the shape container gShapes.push_back( gShape );
// Repaint the window so the new permanent shape will // be displayed.
// Draw all the permenent shapes
for(int i = 0; i < gShapes.size(); ++i) gShapes[i]->draw(hdc);
// Forward any other messages we didn't handle to the
// default window procedure
return DefWindowProc(hWnd, msg, wParam, lParam);
}
Trang 9// WinMain: Entry point for a Windows application
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR cmdLine, int showCmd)
wc.hIcon = ::LoadIcon(0, IDI_APPLICATION);
wc.hCursor = ::LoadCursor(0, IDC_ARROW);
// Step 4: Create the window, and save handle in globla
// window handle variable ghMainWnd
ghMenu = LoadMenu(ghAppInst, MAKEINTRESOURCE(IDR_MENU1));
ghMainWnd = ::CreateWindow("MyWndClassName", "My Paint Program",
// Step 6: Enter the message loop and don't quit until
// a WM_QUIT message is received
Trang 1015.8 Summary
1 Windows sends a window a WM_PAINT message whenever a window needs to repaint its client area (or a region of the client area) A window may need to repaint its client area when it is resized, or when a previously obscured part of it becomes visible The program can explicitly send a WM_PAINT message with the InvalidateRect function A Windows program should know how to draw all of its data, so that if all or part of its client area drawing data is erased (by resizing the window or obscuring the window, for example) it can all be restored at the appropriate time when the next WM_PAINT is sent to the window
2 A device context is a software abstraction of a display device, such as the video card or a printer
We do all drawing operations through the device context (abbreviated as DC) As such, we must pass a handle to a device context (HDC) to all GDI related functions One way to obtain an HDC is with the BeginPaint function
3 To draw text onto the client area of a window, we use the TextOut function To draw a line onto the client area of a window, we use the MoveToEx function to specify the line’s start point, and the LineTo function to specify the line’s end point To draw a rectangle onto the client area
of a window, we use the Rectangle function Finally, to draw an ellipse onto the client area of
a window, we use the Ellipse function Recall that when specifying an ellipse we specify its bounding rectangle; that is, an ellipse that fits tightly into that bounding rectangle will be drawn
4 A bitmap is a matrix of color elements, or pixels To load a bitmap we must first load the bmp file as a resource After that, we can load the bitmap resource into our program and obtain a handle to it with the LoadBitmap function To copy the pixels in the bitmap to the window’s client area we use the BitBlt function When we are finished with a bitmap, we should delete
it with the DeleteObject function
5 Pens and brushes allow us to alter the way in which shapes are drawn with GDI In particular,
we can draw shapes with different colors, styles, and patterns Pens are used to draw lines, curves, and also to draw the border of solid shapes like rectangles and ellipses The properties of
a pen are described with the LOGPEN structure Brushes are used to fill in the interiors of solid shapes like rectangles and ellipses The properties of a brush are described with the LOGBRUSH
structure
6 Menus allow the user to make selections To load a menu, we must first create a menu resource
in the Visual C++ resource editor After we have finished editing the menu resource, we can load it with the LoadMenu function To attach the menu to a window, we can pass in a handle to the menu (HMENU returned from LoadMenu) to the hMenu parameter of CreateWindow We can check/uncheck a menu item with the CheckMenuItem function When the user selects a menu item, Windows sends the window a WM_COMMAND message The lower 16-bits of the
wParam parameter of this message specify the numeric resource ID of the menu item that was selected In this way, we can test which menu item was selected and execute the appropriate consequential code
Trang 1115.9.3 Cube
Figure 15.18 shows a cube As you can see, a cube is made up of twelve lines Write a function that draws a cube to the client area of a window Then add a new “cube” primitive to Program 15.5 that the user can select from the menu and draw
Figure 15.18: A cube outline drawn with twelve lines
15.9.4 Undo Feature
Modify Program 15.5 by implementing an undo feature that erases the last shape that was drawn (add an undo menu item) Be sure to avoid memory leaks
Trang 12Chapter 16
Introduction to Dialogs and Controls
Trang 13Introduction
The final Win32 API-related topics we need to discuss are dialog boxes and controls A dialog box is a type of window, which is commonly used to display a variety of controls the user can set and configure Figure 16.1 shows a dialog box that is used in the demos programs for the DirectX Graphics Programming course here at Game Institute
Figure 16.1: An example of a dialog box with controls
As you can see from Figure 16.1, controls and dialog boxes go hand in hand
Chapter Objectives
• Learn how to create modal and modeless dialog boxes, and how to distinguish between the two
• Discover how to create and design dialog boxes with the Visual C++ resource editor
• Become familiar with several Win32 controls such as static text controls, picture box controls, edit box controls, radio button controls, button controls, and combo box controls
Note: As with the previous two Win32 API chapters in this book, we cannot possibly cover everything
Therefore, if you are interested in learning how to create the various other Win32 controls you may have seen, but which we do not cover here (e.g., slider controls, color controls, spin controls, tree controls), then you will need to consult MSDN or a book devoted to the Win32 API The standard “bible” text on the Win32 API is Programming Windows 5 th Edition By Charles Petzold
Trang 1416.1 Modal Dialog Boxes; The Static Text Control; The Button Control
A modal dialog box is a dialog box that does not close until the user has made some sort of selection
(i.e., the user cannot switch to another window until the modal dialog box has ended) Typically, an application will use a modal dialog box when it needs immediate input from the user, and cannot continue until it receives that input For our first program, we will illustrate a modal dialog box by creating a simple window that can display an “About” dialog box Figure 16.2 shows our goal:
Figure 16.2: The dialog box is launched by going to File and selecting the “About” menu item
16.1.1 Designing the Dialog Box
Dialogs are created and edited in the Visual C++ Resource editor, just like menus and icons Select the
Add Resource button and select a “Dialog” from the “Add Resource” dialog and then select the “New”
button—Figure 16.3
Trang 15Figure 16.3: Select “Dialog” and then press the “New” button
At this point, the Dialog Editor will open up Before we begin designing the dialog box, let us rename the dialog resource ID to IDD_ABOUTBOX—Figure 16.4
Figure 16.4: Renaming the dialog resource ID to IDD_ABOUTBOX
Now, design your dialog box as Figure 16.5 shows