You now know how to create a simple MFC application without resorting to AppWizard. You managed to get a window up on the screen and even got the window to respond to mouse clicks, thanks to MFC's message maps. In this chapter, you extend your knowledge of MFC by seeing how to use device contexts and GDI objects in order to create your window's display.
As you know, every Windows application (in fact, every computer application) must manipulate data in some way. Most applications must also display data. Unfortunately though, because of Windows' device independence, this task is not as straightforward in Windows as it is in DOS.
Although device independence forces you, the programmer, to deal with data displays indirectly, it helps you by ensuring that your programs run on all popular devices. In most cases, Windows handles devices for you through the device drivers that the user has installed on the system. These device drivers intercept the data the application needs to display and translates the data appropriately for the device on which it will appear, whether that is a screen, a printer, or some other output device.
To understand how all of this device independence works, imagine an art teacher trying to design a course of study appropriate for all types of artists. The teacher creates a course outline that stipulates the subject of a project, the suggested colors to be used, the dimensions of the finished project, and so on. What the teacher doesn't stipulate is the surface on which the project will be painted or the materials needed to paint on that surface. In other words, the teacher stipulates only general characteristics. The details of how these characteristics are applied to the finished project are left up to each artist.
The instructor in the preceding scenario is much like a Windows programmer. The programmer has no idea who might eventually use the program and what kind of system that user might have. The programmer can recommend the colors in which data should be displayed and the coordinates at which the data should appear, for example, but it is the device driverthe Windows artistthat ultimately decides how the data appears.
A system with a VGA monitor might display data with fewer colors than a system with a Super VGA monitor. Likewise, a system with a monochrome monitor displays the data in only a single color. Monitors with high resolutions can display more data than lower-resolution monitors. The device drivers, much like the artists in the imaginary art school, must take the display requirements and fine-tune them to the device on which the data will actually appear. And it is a data structure called a device context that links the application to the device's driver.
A device context (DC) is little more than a data structure that keeps track of the attributes of a window's drawing surface. These attributes include the currently selected pen, brush, and font that will be used to draw on the screen. Unlike an artist, who can have many brushes and pens with which to work, a DC can use only a single pen, brush, or font at a time. If you want to use a pen that draws wider lines, for example, you need to create the new pen and then replace the DC's old pen with the new one. Similarly, if you want to fill shapes with a red brush, you must create the brush and "select it into the DC," which is how Windows programmers describe replacing a tool in a DC.
A window's client area is a versatile surface that can display anything a Windows program can draw. The client area can display any type of data because everything displayed in a window, whether it be text, spreadsheet data, a bitmap, or any other type of data, is displayed graphically. MFC helps you display data by encapsulating Windows' GDI functions and objects into its DC classes.
The sample program in this chapter shows you how to use MFC to display many types of data in an application's window. When you run the program, the main window appears, showing text drawn in various sized fonts (see Figure 5.1).
You can draw text using various sized fonts.
When you left-click in the application's window, it switches the type of information it displays. For example, the first time you click, the window shows a series of blue lines that get thicker as they get closer to the window's bottom (see Figure 5.2). This screen is produced by creating new pens and drawing the lines with those pens.
An application can also display many different types of lines.
A second click brings up a display consisting of rectangles, each of which contains a different pattern (see Figure 5.3). The fill patterns in the rectangles are produced by the brushes created in the program. Finally, a third click brings you back to the font display.
Different brushes enable your window to display colors and patterns.
Listing 5.1 through Listing 5.4 are the source codes for the Paint1 application. Take some time now to run the application, as well as to look over the source code in the listings. In the sections that follow, you'll learn how the program produces its displays and how MFC simplifies the drawing of a window's displays.
Listing 5.1 PAINT1.HThe Header File of the Application Class
/////////////////////////////////////////////////////////// // PAINT1.H: Header file for the CPaintApp1 class, which // represents the application object. /////////////////////////////////////////////////////////// class CPaintApp1 : public CWinApp { public: CPaintApp1(); // Virtual function overrides. BOOL InitInstance(); };
Listing 5.2 PAINT1.CPPThe Implementation File of the Application Class
/////////////////////////////////////////////////////////// // PAINT1.CPP: Implementation file for the CPaintApp1, // class, which represents the application // object. /////////////////////////////////////////////////////////// #include <afxwin.h> #include "paint1.h" #include "mainfrm.h" // Global application object. CPaintApp1 PaintApp1; /////////////////////////////////////////////////////////// // Construction/Destruction. /////////////////////////////////////////////////////////// CPaintApp1::CPaintApp1() { } /////////////////////////////////////////////////////////// // Overrides /////////////////////////////////////////////////////////// BOOL CPaintApp1::InitInstance() { m_pMainWnd = new CMainFrame(); m_pMainWnd->ShowWindow(m_nCmdShow); m_pMainWnd->UpdateWindow(); return TRUE; }
Listing 5.3 MAINFRM.HThe Header File of the Frame Window
/////////////////////////////////////////////////////////// // MAINFRM.H: Header file for the CMainFrame class, which // represents the application's main window. /////////////////////////////////////////////////////////// // Enumeration for GDI object to display. enum {Fonts, Pens, Brushes}; class CMainFrame : public CFrameWnd { // Protected data members. protected: UINT m_display; // Constructor and destructor. public: CMainFrame(); ~CMainFrame(); // Overrides. protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs); // Message map functions. public: afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); // Protected member functions. protected: void ShowFonts(CPaintDC* paintDC); void ShowPens(CPaintDC* paintDC); void ShowBrushes(CPaintDC* paintDC); DECLARE_MESSAGE_MAP() };
Listing 5.4 MAINFRM.CPPThe Implementation File of the Frame Window Class
/////////////////////////////////////////////////////////// // MAINFRM.CPP: Implementation file for the CMainFrame // class, which represents the application's // main window. /////////////////////////////////////////////////////////// #include <afxwin.h> #include "mainfrm.h" BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_PAINT() ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() /////////////////////////////////////////////////////////// // CMainFrame: Construction and destruction. /////////////////////////////////////////////////////////// CMainFrame::CMainFrame() { Create(NULL, "Paint1", WS_OVERLAPPED | WS_SYSMENU); m_display = Fonts; } CMainFrame::~CMainFrame() { } /////////////////////////////////////////////////////////// // Overrides. /////////////////////////////////////////////////////////// BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { // Set size of the main window. cs.cx = 440; cs.cy = 460; // Call the base class's version. BOOL returnCode = CFrameWnd::PreCreateWindow(cs); return returnCode; } /////////////////////////////////////////////////////////// // Message map functions. /////////////////////////////////////////////////////////// void CMainFrame::OnPaint() { CPaintDC* paintDC = new CPaintDC(this); switch (m_display) { case Fonts: ShowFonts(paintDC); break; case Pens: ShowPens(paintDC); break; case Brushes: ShowBrushes(paintDC); break; } delete paintDC; } void CMainFrame::OnLButtonDown(UINT nFlags, CPoint point) { if (m_display == Fonts) m_display = Pens; else if (m_display == Pens) m_display = Brushes; else m_display = Fonts; Invalidate(); } /////////////////////////////////////////////////////////// // Protected member functions. /////////////////////////////////////////////////////////// void CMainFrame::ShowFonts(CPaintDC* paintDC) { // Initialize a LOGFONT structure for the fonts. LOGFONT logFont; logFont.lfHeight = 8; logFont.lfWidth = 0; logFont.lfEscapement = 0; logFont.lfOrientation = 0; logFont.lfWeight = FW_NORMAL; logFont.lfItalic = 0; logFont.lfUnderline = 0; logFont.lfStrikeOut = 0; logFont.lfCharSet = ANSI_CHARSET; logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; logFont.lfQuality = PROOF_QUALITY; logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN; strcpy(logFont.lfFaceName, "Times New Roman"); // Initialize the position of text in the window. UINT position = 0; // Create and display eight example fonts. for (UINT x=0; x<8; ++x) { // Set the new font's height. logFont.lfHeight = 16 + (x * 8); // Create a new font and select it into the DC. CFont* font = new CFont(); font->CreateFontIndirect(&logFont); CFont* oldFont = paintDC->SelectObject(font); // Print text with the new font. position += logFont.lfHeight; paintDC->TextOut(20, position, "A sample font."); // Restore the old font to the DC. paintDC->SelectObject(oldFont); delete font; } } void CMainFrame::ShowPens(CPaintDC* paintDC) { // Initialize the line position. UINT position = 10; // Draw sixteen lines in the window. for (UINT x=0; x<16; ++x) { // Create a new pen and select it into the DC. CPen* pen = new CPen(PS_SOLID, x*2+1, RGB(0, 0, 255)); CPen* oldPen = paintDC->SelectObject(pen); // Draw a line with the new pen. position += x * 2 + 10; paintDC->MoveTo(20, position); paintDC->LineTo(400, position); // Restore the old pen to the DC. paintDC->SelectObject(oldPen); delete pen; } } void CMainFrame::ShowBrushes(CPaintDC* paintDC) { // Initialize the rectangle position. UINT position = 0; // Draw seven rectangles. for (UINT x=0; x<7; ++x) { CBrush* brush; // Create a solid or hatched brush. if (x == 6) brush = new CBrush(RGB(0,255,0)); else brush = new CBrush(x, RGB(0,160,0)); // Select the new brush into the DC. CBrush* oldBrush = paintDC->SelectObject(brush); // Draw the rectangle. position += 50; paintDC->Rectangle(20, position, 400, position + 40); // Restore the DC and delete the brush. paintDC->SelectObject(oldBrush); delete brush; } }
The complete source code and executable file for the Paint1 application can be found in the CHAP05\PAINT1 directory of the CD-ROM included with this book.
If you took the time to look over the listings that constitute the Paint1 application, you're either now smugly confident that you know all there is to know about displaying data in a window or you feel like you just tried to read the latest John Grisham novel in Latin. Whichever category you fall into, you'll almost certainly want to read on. You smug folks might get some surprises, whereas the rest of you will discover that the code isn't anywhere near as complex as it might appear at first glance.
In Chapter 4, "Constructing an MFC Program from Scratch," you learned about message maps and how you can tell MFC which functions to call when it receives messages from Windows. One important message that every Windows program with a window must handle is WM_PAINT. Windows sends the WM_PAINT message to an application's window when the window needs to be redrawn. There are several events that cause Windows to send a WM_PAINT message. The first event is simply the running of the program by the user. In a properly written Windows application, the application's window gets a WM_PAINT message almost immediately after being run to ensure that the appropriate data is displayed from the very start.
Another time that a window might receive the WM_PAINT message is when the window has been resized or has recently been uncoveredeither fully or partiallyby another window. In either case, part of the window that wasn't visible before is now on the screen and must be updated.
Finally, a program can indirectly send itself a WM_PAINT message by invalidating its client area. Having this capability ensures that an application can change its window's contents almost any time it wants. For example, a word processor might invalidate its window after the user pastes some text from the Clipboard.
When you studied message maps, you learned to convert a message name to a message map macro and function name. You now know, for example, that the message map macro for a WM_PAINT message is ON_WM_PAINT(). You also know that the matching message map function should be called OnPaint(). This is another case when MFC has already done most of the work of matching a Windows message with its message response function.
See Constructing an MFC Program from Scratch, (ch. 4)
So, in order to paint your window's display, you need to add an ON_WM_PAINT() entry to your message map and then write an OnPaint() function. In the OnPaint() function, you write the code that will produce your window's display. Then, whenever Windows sends your application the WM_PAINT message, MFC will automatically call OnPaint(), which will draw the window's display just as you want it.
If you look near the top of Listing 5.4, which is the implementation file of the frame window class, you'll see the application's main message map, as shown in Listing 5.5.
Listing 5.5 LST05_05.CPPThe Message Map of the Paint1 Application
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_WM_PAINT() ON_WM_LBUTTONDOWN() END_MESSAGE_MAP()
As you can tell by the message map's entries, the application can respond to WM_PAINT and WM_LBUTTONDOWN messages. The ON_WM_PAINT() entry maps to the OnPaint() message map function. In the first line of that function, the program creates a DC for the client area of the frame window:
CPaintDC* paintDC = new CPaintDC(this);
CPaintDC is a special class for managing paint DCs, which are device contexts that are used only when responding to WM_PAINT messages. In fact, if you're going to use MFC to create your OnPaint() function's paint DC, you must use the CPaintDC class. This is because an object of the CPaintDC class does more than just create a DC; it also calls the BeginPaint() Windows API function in the class's constructor and calls EndPaint() in its destructor. When responding to WM_PAINT messages, calls to BeginPaint() and EndPaint() are required. The CPaintDC class handles this requirement without your having to get involved in all the messy details.
As you can see, the CPaintDC constructor takes a single argument, which is a pointer to the window for which you're creating the DC. Because the preceding code line uses the new operator to create the CPaintDC object dynamically on the heap, the program must call delete on the returned CPaintDC pointer when the program is finished with the DC. Otherwise you, not only leave unused objects floating around the computer's memory, but you also fail to call the CPaintDC object's constructor, which in turn never gets a chance to call EndPaint().
After creating the paint DC, the OnPaint() function in Listing 5.4 uses its m_display data member to determine what type of display to draw in the window. The m_display data member can be equal to Fonts, Pens, or Brushes, three values that are defined as an enumeration in the MAINFRM.H file, like this:
enum {Fonts, Pens, Brushes};
The function checks m_display in a switch statement, calling ShowFonts(), ShowPens(), or ShowBrushes() as appropriate. It is in these three functions that the Paint1 application actually creates its displays. You'll examine these functions of your CMainFrame class in the sections to come. Notice that the pointer to the paint DC is passed as a parameter to ShowFonts(), ShowPens(), and ShowBrushes(), which must use the DC in order to draw in the application's window.
The last line in OnPaint() deletes the CPaintDC object, paintDC, freeing it from memory, as well as ensuring that its destructor gets called properly. Note that, in most programs, you might find it more convenient to create your paint DC as a local variable on the stack, as is shown in Listing 5.6. When you do this, you no longer have to be concerned with deleting the object. It's automatically deleted when it goes out of scope. In the case of the Paint1 application, it's more efficient to pass a CPaintDC pointer as an argument to other functions than it is to pass the actual object.
Listing 5.6 LST05_06.CPPCreating the PaintDC on the Stack
void CMainFrame::OnPaint() { CPaintDC paintDC(this); // Drawing code goes here. }
Fonts are one of the trickier GDI objects to handle, so you might as well get them out of the way first. In order to select and use fonts, you must be familiar with the LOGFONT structure, which contains a wealth of information about a font, and you must know how to create new fonts when they're needed. Moreover, there are more typefaces and font types than galaxies in the universe (okay, maybe not quite that many), which means you can never be sure exactly how your user's system is set up.
As I said, a Windows font is described in the LOGFONT structure, which is outlined in Table 5.1. The LOGFONT description in Table 5.1, however, gives only an overview of the structure. Before experimenting with custom fonts, you might want to look up this structure in your Visual C++ online help, where you'll find a more complete description of each of its fields, including the many constants that are already defined for use with the structure.
Table 5.1 LOGFONT Fields and Their Descriptions
Field | Description |
lfHeight | Height of the font in logical units |
lfWidth | Width of the font in logical units |
lfEscapement | Angle at which to draw the text |
lfOrientation | Character tilt in tenths of a degree |
lfWeight | Field used to select normal (400) or boldface (700) text |
lfItalic | A non-zero value indicates italics |
lfUnderline | A non-zero value indicates an underlined font |
lfStrikeOut | A non-zero value indicates a strikethrough font |
lfCharSet | Font character set |
lfOutPrecision | How to match the requested font to the actual font |
lfClipPrecision | How to clip characters that run over the clip area |
lfQuality | Print quality of the font |
lfPitchAndFamily | Pitch and font family |
lfFaceName | Typeface name |
The LOGFONT structure holds a complete description of the font. This structure contains fourteen fields, although many of the fields can be set to 0 or the default values, depending on the program's needs. In the ShowFonts() function, the Paint1 application creates its LOGFONT, as shown in Listing 5.7.
Listing 5.7 LST05_07.CPPInitializing a LOGFONT Structure
LOGFONT logFont; logFont.lfHeight = 8; logFont.lfWidth = 0; logFont.lfEscapement = 0; logFont.lfOrientation = 0; logFont.lfWeight = FW_NORMAL; logFont.lfItalic = 0; logFont.lfUnderline = 0; logFont.lfStrikeOut = 0; logFont.lfCharSet = ANSI_CHARSET; logFont.lfOutPrecision = OUT_DEFAULT_PRECIS; logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; logFont.lfQuality = PROOF_QUALITY; logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN; strcpy(logFont.lfFaceName, "Times New Roman");
In the preceding lines, the font is set to be eight pixels high, as determined by the value of the lfHeight field. Note that, in most cases, you should set the width to 0, as determined by lfWidth, which enables Windows to select a width that best matches the height. You can, however, create compressed or expanded fonts by experimenting with the lfWidth field.
The font's italic, underline, and strikeout attributes can be turned on by supplying a non-zero value for the lfItalic, lfUnderline, and lfStrikeOut fields. For example, Figure 5.4 shows how the Paint1 application's font display would look if you set the lfItalic and lfUnderline members of the LOGFONT structure to 1.
You can create all types of fonts by manipulating the values in the LOGFONT structure.
In order to show the many fonts that are displayed in its window, the Paint1 application creates its fonts in a for loop, modifying the value of the LOGFONT structure's lfHeight member each time through the loop, using the loop variable, x, to calculate the new font height, like this:
logFont.lfHeight = 16 + (x * 8);
Because x starts at zero, the first font created in the loop will be sixteen pixels high. Each time through the loop, the new font will be eight pixels higher than the previous one.
After setting the font's height, the program creates a CFont object:
CFont* font = new CFont();
In case it's not obvious, CFont is MFC's font class. Using the CFont class, you can create and manipulate fonts using the class's member functions. One of the most important member functions is CreateFontIndirect(), which DisplayFonts() calls like this:
font->CreateFontIndirect(&logFont);
CreateFontIndirect() takes a single argument, which is the address of the LOGFONT structure that contains the font's attributes. When Windows receives the information stored in the LOGFONT structure, it will do its best to create the requested font. The font created isn't always exactly the font requested, so Windows fills in the LOGFONT structure with a description of the font it managed to create.
After calling CreateFontIndirect(), the CFont object has been associated with a Windows font. At this point, you can select the font into the DC, like this:
CFont* oldFont = paintDC->SelectObject(font);
Remember that in order to use a new graphical object with a DC, you must first select that object into the DC. The above call to the paint DC's SelectObject() member function replaces the current font in the DC with the new one. SelectObject()'s single parameter is the address of the new font. SelectObject() returns a pointer to the old font object that was deselected from the DC. You'll soon see why you must save this pointer.
After selecting the new font into the DC, you can use the font to draw text on the screen. In the ShowFonts() function, the first step in displaying text is to determine where in the window to draw the text:
position += logFont.lfHeight;
The local variable position holds the vertical position in the window at which the next line of text should be printed. This position depends upon the height of the current font. After all, if there's not enough space between the lines, the larger fonts will overlap the smaller ones. When Windows created the new font, it stored the font's height (which is most likely the height you requested, but, then again, maybe not) in the LOGFONT structure's lfHeight member. By adding the value stored in lfHeight, the program can determine the next position at which to display the line of text, using the DC object's TextOut() member function:
paintDC->TextOut(20, position, "A sample font.");
Here, TextOut()'s three arguments are the X,Y coordinates in the window at which to print the text and the text to print. TextOut() actually has a fourth argument, which is the number of characters to print. If you leave this last parameter off, MFC just assumes you want to display the entire string given as the second argument.
Now you get to see why the program saved the pointer of the old font that was deselected from the DC when the program selected the new font. You must never delete a GDI object, such as a font, from a DC while it's still selected into the DC. That means you must first deselect the new font from the DC before deleting it in preparation for creating the next font. Unfortunately, if you search through your Windows programming manuals, you'll discover that there is no DeselectObject() function. This actually makes sense when you think about it. If you were able to deselect GDI objects without selecting new ones, you could leave the DC without a pen, brush, or other important object. So, the only way to deselect an object is to select a new object into the DC. Therefore, to deselect the new font, the Paint1 application selects the old font back into the DC, like this:
paintDC->SelectObject(oldFont);
This time the program doesn't bother to save the pointer returned from SelectObject() because that pointer is for the new font that the program created. The program already has that pointer stored in font, so a quick call to delete gets rid of the font object. Consequently, the program can create the next font it needs for the display:
delete font;
You'll be pleased to know that pens are much easier to deal with than fonts, mostly because you don't have to fool around with complicated data structures like LOGFONT. In fact, to create a pen, you need only supply the pen's line style, thickness, and color. In its window, the Paint1 application's ShowPens() function displays lines drawn using different pens created within a for loop. Within the loop, the program first creates a custom pen, like this:
CPen* pen = new CPen(PS_SOLID, x*2+1, RGB(0, 0, 255));
The first argument just shown is the line's style, which can be one of the styles listed in Table 5.2. Note that only solid lines can be drawn with different thicknesses. Patterned lines always have a thickness of 1. The second argument above is the line thickness, which, in the ShowPens() function, is calculated using the loop variable x as a multiplier.
Finally, the third argument is the line's color. The RGB macro takes three values for the red, green, and blue color components and converts them into a valid Windows color reference. The values for the red, green, and blue color components can be anything from zero to 255the higher the value, the brighter the color component. The preceding line creates a bright blue pen. If all the color values were zero, the pen would be black, whereas if the color values were all 255, the pen would be white.
Table 5.2 Pen Styles
Style | Meaning |
PS_DASH | Specifies a pen that draws dashed lines |
PS_DASHDOT | Specifies a pen that draws dash-dot patterned lines |
PS_DASHDOTDOT | Specifies a pen that draws dash-dot-dot patterned lines |
PS_DOT | Specifies a pen that draws dotted lines |
PS_INSIDEFRAME | Specifies a pen that's used with shapes, where the line's thickness must not extend outside of the shape's frame. |
PS_NULL | Specifies a pen that draws invisible lines |
PS_SOLID | Specifies a pen that draws solid lines |
After creating the new pen, the program selects it into the DC, saving the pointer to the old pen, like this:
CPen* oldPen = paintDC->SelectObject(pen);
After the pen is selected into the DC, the program can draw a line with the pen. To do this, the program first calculates a vertical position for the new line and then calls the paint DC's MoveTo() and LineTo() member functions, like this:
position += x * 2 + 10; paintDC->MoveTo(20, position); paintDC->LineTo(400, position);
The MoveTo() function positions the starting point of the line, whereas the LineTo() function draws a lineusing the pen currently selected into the DCfrom the point set with MoveTo() to the coordinates given as the function's two arguments.
Finally, the last step is to restore the DC by reselecting the old pen and deleting the new pen, which is no longer selected into the DC:
paintDC->SelectObject(oldPen); delete pen;
If you want to control the style of a line's end points or want to create your own custom patterns for pens, you can use the alternate CPen constructor, which requires a few more arguments than the CPen constructor described in this section. To learn how to use this alternate constructor, look up CPen in your Visual C++ online documentation.
Creating and using brushes in an MFC program is not unlike using pens. In fact, just as with pens, you can create both solid and patterned brushes. You can even create brushes from bitmaps that contain your own custom fill patterns. As you've seen, the Paint1 application displays rectangles that have been filled by both patterned and solid rectangles. These rectangles are produced in the ShowBrushes() function, which, like the font and pen functions you've already examined, creates its graphical objects within a for loop. In the first line of the loop's body, the program defines a pointer to a CBrush object:
CBrush* brush;
Then, depending on the value of the loop variable x, the program creates either a solid or a patterned brush, as shown in Listing 5.8.
Listing 5.8 LST05_08.CPPCreating CBrush Objects
if (x == 6) brush = new CBrush(RGB(0,255,0)); else brush = new CBrush(x, RGB(0,160,0));
In Listing 5.8, if x equals 6, the program calls the version of the CBrush constructor that creates a solid pen. The constructor's single argument is a COLORREF value, which is easily produced using the RGB macro to which you were introduced in the section Using Pens. For any other value of x, the program creates a patterned brush, using x as the pattern index. The second CBrush constructor takes the pattern index and the brush color as its two arguments. Although not used in the preceding code segment, Windows defines several constants for the brush patterns (or hatch styles, as they're often called). Those constants are HS_BDIAGONAL, HS_CROSS, HS_DIAGCROSS, HS_FDIAGONAL, HS_HORIZONTAL, and HS_VERTICAL.
After the program has created the new brush, a call to the paint DC's SelectObject() member function selects the brush into the DC and returns a pointer to the old brush:
CBrush* oldBrush = paintDC->SelectObject(brush);
Now the program calculates a new drawing position, and then draws a rectangle with the new brush:
position += 50; paintDC->Rectangle(20, position, 400, position + 40);
Rectangle() is just one of the shape-drawing functions you can call. Rectangle() takes as arguments the coordinates of the rectangle's upper-left and lower-right corners. When you run the Paint1 application and look at the brush window, you'll see that each rectangle is bordered by a thin black line. This line was drawn by the DC's default black pen. If you had selected a different pen into the DC, Windows would have used that pen to draw the rectangle's border. For example, in Figure 5.5, the Paint1 program shows rectangles drawn with a six-pixel thick pen.
Shape-drawing functions frequently draw borders with the currently selected pen.
After drawing a rectangle, the program deselects the new brush from the DC and deletes it:
paintDC->SelectObject(oldBrush); delete brush;
As you know, when you click in the Paint1 application's window, the window's display changes. This seemingly magical feat is actually easy to accomplish. The program routes WM_LBUTTONDOWN messages to the OnLButtonDown() message response function, just as the sample program in Chapter 4, Constructing an MFC Program from Scratch, did. But rather than displaying a message box, this version of OnLButtonDown() is charged with keeping the window updated with the selected display. At the program startup, the CMainFrame class's constructor initializes its data member m_display to Fonts so that the window initially appears with the fonts displayed. When the user clicks in the window, the OnLButtonDown() function changes the value of m_display, as shown in Listing 5.9.
Listing 5.9 LST05_09.CPPChanging the Value of m_display
if (m_display == Fonts) m_display = Pens; else if (m_display == Pens) m_display = Brushes; else m_display = Fonts;
As you can see, depending on its current value, m_display gets set to the next display type in the series. Of course, just changing the value of m_display doesn't accomplish much. The program still needs to redraw the contents of its window. Because OnPaint() determines which display to paint based on the value of m_display, all the program needs to do is to get OnPaint() to execute. This task is accomplished by calling the CMainFrame class's Invalidate() function:
Invalidate();
A call to Invalidate() tells Windows that all of the window needs to be repainted. This causes Windows to generate a WM_PAINT message for the window. Thanks to MFC's message mapping, the WM_PAINT message gets routed to OnPaint(). Although it's not used in the preceding example, Invalidate() actually has one argument, which MFC gives the default value of TRUE. This Boolean argument tells Windows whether to erase the window's background. If you use FALSE for this argument, Windows leaves the background alone. In Figure 5.6, you can see what happens to the Paint1 application if Invalidate() gets called with an argument of FALSE.
Without erasing the background, the Paint1 application's window gets a bit messy.
In Chapter 4, you saw how to initially size and position a window when calling the window's Create() member function. You can also size and position a window by overriding the PreCreateWindow() function of the window class. This method of positioning a window is especially useful in an AppWizard-generated program because you don't usually call Create() directly in such applications. Although the Paint1 application wasn't created by AppWizard, it does override its window class's PreCreateWindow() function to position the window, as shown in Listing 5.10.
Listing 5.10 LST05_10.CPPOverriding PreCreateWindow()
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { // Set size of the main window. cs.cx = 440; cs.cy = 460; // Call the base class's version. BOOL returnCode = CFrameWnd::PreCreateWindow(cs); return returnCode; }
The PreCreateWindow() function, which MFC calls right before the window element that'll be associated with the class is created, receives one parameter: a reference to a CREATESTRUCT structure. The CREATESTRUCT structure contains essential information about the window that's about to be created and is declared by Windows, as shown in Listing 5.11.
Listing 5.11 LST05_11.CPPThe CREATESTRUCT Structure
typedef struct tagCREATESTRUCT { LPVOID lpCreateParams; HANDLE hInstance; HMENU hMenu; HWND hwndParent; int cy; int cx; int y; int x; LONG style; LPCSTR lpszName; LPCSTR lpszClass; DWORD dwExStyle; } CREATESTRUCT;
If you've programmed Windows without application frameworks, such as MFC, you'll recognize the information stored in the CREATESTRUCT structure. You supply much of this information when calling the Windows API function CreateWindow() to create your application's window. Of special interest to MFC programmers are the cy, cx, y, and x members of this structure. By changing cy and cx, you can set the width and height, respectively, of the window. Similarly, modifying y and x changes the window's position. By overriding PreCreateWindow(), you get a chance to fiddle with the CREATESTRUCT structure before Windows uses it to create the window.
It's important thatafter your own code in PreCreateWindow()you call the base class's PreCreateWindow(). Failure to do this will leave you without a valid window because MFC never gets a chance to pass the CREATESTRUCT structure on to Windows, so Windows never creates your window. When overriding class member functions, you often need to call the base class's version, either before or after your own code, depending on the function. You'll learn more about this as you work your way through this book. Also, the descriptions of member functions in your Visual C++ online documentation indicate whether or not the base class's version must be called.
You're really starting to master MFC now. Take some time at this point to look over the CDC class and the several classes derived from CDC in your Visual C++ online documentation. You'll discover a wealth of member functions you can use to create displays for your windows. Remember that CPaintDC is just one type of CDC-derived class, one that's used specifically in the OnPaint() message response function. If you want to create a DC to paint your window in some other part of your program, you'll probably want to use the CClientDC class.