摘要: Enough of the preliminaries. By now, you probably feel as if you asked for the time and got an explanation of watchmaking. Everything you've learned so far in this chapter will come in handy sooner or later—trust me. But now let's talk about functions for outputting pixels to the screen. The fun阅读全文
posted @ 2011-01-11 01:52 snddman 阅读(390) 评论(0) 编辑

In a single-tasking environment such as MS-DOS, the name of the game when it comes to screen output is "anything goes." A running application is free to do just about whatever it wants whenever it wants, whether that involves drawing a line on the screen, reprogramming the adapter's color palette, or switching to another video mode. In a windowed, multitasking environment such as Windows, programs can't be afforded such freedom because the output from program A must be protected from the output of program B. First and foremost, this means that each program's output must be restricted to its own window. The GDI uses a simple mechanism to make sure every program that draws in a window plays by the rules. That mechanism is the device context.

When a Windows program draws to a screen, a printer, or any other output device, it doesn't output pixels directly to the device. Instead, it draws to a logical "display surface" represented by a device context (DC). A device context is a data structure deep inside Windows that contains fields describing everything the GDI needs to know about the display surface, including the physical device with which it is associated and assorted state information. Before it draws anything on the screen, a Windows program acquires a device context handle from the GDI. It then passes that handle back to the GDI each time it calls a GDI output function. Without a valid device context handle, the GDI won't draw the first pixel. And through the device context, the GDI can make sure that everything the program draws is clipped to a particular area of the screen. Device contexts play a huge role in making the GDI device-independent because, given a handle to a device context, the same GDI functions can be used to draw to a diverse assortment of output devices.

When you program Windows with MFC, the device context has even greater significance. In addition to serving as the key that unlocks the door to output devices, a device context object encapsulates the GDI functions that programs use to generate output. In MFC, you don't grab a handle to a device context and call GDI output functions, at least not directly; instead, you create a device context object and call its member functions to do your drawing. MFC's CDC class wraps a Windows device context and the GDI functions that require a device context handle into one convenient package, and CDC-derived classes such as CPaintDC and CClientDC represent the different types of device contexts that Windows applications use.

The MFC Device Context Classes

One way to get a device context in an MFC application is to call CWnd::GetDC, which returns a pointer to a CDC object representing a Windows device context. A device context pointer acquired with CWnd::GetDC should be released with CWnd::ReleaseDC when drawing is completed. The following code gets a CDC pointer from GetDC, does some drawing, and calls ReleaseDC to release the device context:

CDC* pDC = GetDC (); // Do some drawing ReleaseDC (pDC); 

If the same code were to appear in an OnPaint handler, you would use CWnd::BeginPaint and CWnd::EndPaint in place of GetDC and ReleaseDC to ensure proper handling of the WM_PAINT message:

PAINTSTRUCT ps; CDC* pDC = BeginPaint (&ps); // Do some drawing EndPaint (&ps); 

The GDI also supports metafiles, which store sequences of GDI commands that can be "played back" to produce physical output. To acquire a device context for a metafile's output, you would use yet another set of functions to obtain and release the CDC pointer. And to acquire a CDC pointer for a device context that permits drawing anywhere in the window (as opposed to one that permits drawing only in the window's client area), you would call CWnd::GetWindowDC rather than GetDC and release the device context with ReleaseDC.

To save you the trouble of having to remember which functions to call to acquire and release a device context (and to help ensure that a device context is properly released when the message handler that uses the device context ends), MFC provides the CDC-derived classes listed in the following table.

Special-Purpose Device Context Classes

Class Name Description
CPaintDC For drawing in a window's client area (OnPaint handlers only)
CClientDC For drawing in a window's client area (anywhere but OnPaint)
CWindowDC For drawing anywhere in a window, including the nonclient area
CMetaFileDC For drawing to a GDI metafile

These classes are designed to be instantiated directly. Each class's constructor and destructor call the appropriate functions to get and release the device context so that using a device context is no more complicated than this:

CPaintDC dc (this); // Do some drawing 

The pointer passed to the class constructor identifies the window that the device context pertains to.

When a device context object is constructed on the stack, its destructor is called automatically when the object goes out of scope. And when the destructor is called, the device context is released back to Windows. The only time you need to be concerned about releasing one of these device contexts yourself is when (and if) you create a device context object on the heap with new, as shown here:

CPaintDC* pDC = new CPaintDC (this); 

In this case, it's important to execute a

delete pDC; 

statement before the function that created the device context ends so that the object's destructor will be called and the device context will be released. On some occasions, it's useful to create a device context on the heap rather than on the stack, but generally you're a lot better off creating device context objects on the stack and letting the compiler do the deleting for you.

The CPaintDC Class

MFC's CPaintDC class lets you paint in a window's client area in response to WM_PAINT messages. You should use it only in OnPaint handlers and never anywhere else. WM_PAINT messages are different from all other Windows messages in one very important respect: If the handler fails to call the Windows ::BeginPaint and ::EndPaint functions (or the MFC equivalents, CWnd::BeginPaint and CWnd::EndPaint), the message will not be removed from the message queue no matter how much drawing you do. Consequently, the application will get stuck processing the same WM_PAINT message over and over. CPaintDC virtually ensures that this won't happen by calling ::BeginPaint and ::EndPaint from its constructor and destructor, respectively.

The CClientDC and CWindowDC Classes

Windows programs don't always limit their painting to OnPaint. If you write an application that draws a circle on the screen whenever a mouse button is clicked, you'll probably want to paint the circle immediately—when you receive the button-click message—rather than wait for the next WM_PAINT message.

That's what MFC's CClientDC class is for. CClientDC creates a client-area device context that can be used outside OnPaint. The following message handler uses CClientDC and two CDC member functions to draw an X connecting the corners of the window's client area when the left mouse button is clicked:

void CMainWindow::OnLButtonDown (UINT nFlags, CPoint point) {     CRect rect;     GetClientRect (&rect);      CClientDC dc (this);     dc.MoveTo (rect.left, rect.top);     dc.LineTo (rect.right, rect.bottom);     dc.MoveTo (rect.right, rect.top);     dc.LineTo (rect.left, rect.bottom); } 

left, right, top, and bottom are public member variables defined in MFC's CRect class. They store the coordinates of the rectangle's four sides. MoveTo and LineTo are line-drawing functions that CClientDC inherits from CDC. You'll learn more about these two functions in a moment.

For the rare occasions on which you'd like to paint not only the window's client area but also the nonclient area (the title bar, the window border, and so on), MFC provides the CWindowDC class. CWindowDC is similar to CClientDC, but the device context it represents encompasses everything within the window's borders. Programmers sometimes use CWindowDC for unusual effects such as custom-drawn title bars and windows with rounded corners. In general, you won't need CWindowDC very often. If you do want to do your own painting in a window's nonclient area, you can trap WM_NCPAINT messages with an OnNcPaint handler to determine when the nonclient area needs to be painted. Unlike OnPaint, an OnNcPaint handler need not (and should not) call BeginPaint and EndPaint.

For the even rarer occasions on which a program requires access to the entire screen, you can create a CClientDC or CWindowDC object and pass its constructor a NULL pointer. The statements

CClientDC dc (NULL); dc.Ellipse (0, 0, 100, 100); 

draw a circle in the upper left corner of the screen. Screen capture programs frequently use full-screen DCs to access the whole screen. Needless to say, drawing outside your own window is a very unfriendly thing to do unless you have a specific reason for doing so.

Device Context Attributes

When you draw to the screen with CDC output functions, certain characteristics of the output aren't specified in the function call but are obtained from the device context itself. When you call CDC::DrawText, for example, you specify the text string and the rectangle in which the string will appear, but you don't specify the text color or the font because both are attributes of the device context. The following table lists some of the most useful device context attributes and the CDC functions used to access them.

Key Device Context Attributes

Attribute Default Set with Get with
Text color Black CDC::SetTextColor CDC::GetTextColor
Background color White CDC::SetBkColor CDC::GetBkColor
Background mode OPAQUE CDC::SetBkMode CDC::GetBkMode
Mapping mode MM_TEXT CDC::SetMapMode CDC::GetMapMode
Drawing mode R2_COPYPEN CDC::SetROP2 CDC::GetROP2
Current position (0,0) CDC::MoveTo CDC::GetCurrentPosition
Current pen BLACK_PEN CDC::SelectObject CDC::SelectObject
Current brush WHITE_BRUSH CDC::SelectObject CDC::SelectObject
Current font SYSTEM_FONT CDC::SelectObject CDC::SelectObject

Different CDC output functions use device context attributes in different ways. For example, when you draw a line with LineTo, the current pen determines the line's color, width, and style (solid, dotted, dashed, and so on). Similarly, when you draw a rectangle with the Rectangle function, the GDI borders the rectangle with the current pen and fills the rectangle with the current brush. All text output functions use the current font. The text color and the background color control the colors used when text is output. The text color determines the color of the characters, and the background color determines what color is used to fill behind them. The background color is also used to fill the gaps between line segments when dotted or dashed lines are drawn with the LineTo function and to fill the open areas between hatch marks painted by a hatch brush. If you'd like the background color to be ignored entirely, you can set the background mode to "transparent," like this:

dc.SetBkMode (TRANSPARENT); 

Inserting this statement before the call to DrawText in Chapter 1's Hello program eliminates the white rectangle surrounding "Hello, MFC" that's visible when the window background color is nonwhite.

The CDC function you'll use more than any other to modify the attributes of a device context is SelectObject. The following six items are GDI objects that can be selected into a device context with SelectObject:

  • Pens
  • Brushes
  • Fonts
  • Bitmaps
  • Palettes
  • Regions

In MFC, pens, brushes, and fonts are represented by the classes CPen, CBrush, and CFont. (Bitmaps, palettes, and regions are discussed in Chapter 15.) Unless you call SelectObject to change the current pen, brush, or font, the GDI uses the device context's defaults. The default pen draws solid black lines 1 pixel wide. The default brush paints solid white. The default font is a rather plain proportional font with a height of roughly 12 points. You can create pens, brushes, and fonts of your own and select them into a device context to change the attributes of the output. To draw a solid red circle with a 10-pixel-wide black border, for example, you can create a black pen 10 pixels wide and a red brush and select them into the device context with SelectObject before calling Ellipse. If pPen is a pointer to a CPen object, pBrush is a pointer to a CBrush object, and dc represents a device context, the code might look like this:

dc.SelectObject (pPen); dc.SelectObject (pBrush); dc.Ellipse (0, 0, 100, 100); 

SelectObject is overloaded to accept pointers to objects of various types. Its return value is a pointer to the object of the same type that was previously selected into the device context.

Each time you acquire a device context from Windows, its attributes are reset to the defaults. Consequently, if you want to use a red pen and a blue brush to paint your window in response to WM_PAINT messages, you must select them into the device context each time OnPaint is called and a new CPaintDC object is created. Otherwise, the default pen and brush will be used. If you'd like to avoid reinitializing a device context every time you use it, you can save its state with the CDC::SaveDC function and restore it the next time around with CDC::RestoreDC. Another option is to register a custom WNDCLASS that includes the CS_OWNDC style, which causes Windows to allocate to each instance of your application its own private device context that retains its settings. (A related but seldom used WNDCLASS style, CS_CLASSDC, allocates a "semiprivate" device context that is shared by all windows created from the same WNDCLASS.) If you select a red pen and a blue brush into a private device context, they remain selected until they're explicitly replaced.

The Drawing Mode

When the GDI outputs pixels to a logical display surface, it doesn't simply output pixel colors. Rather, it combines the colors of the pixels that it's outputting with the colors of the pixels at the destination using a combination of Boolean operations. The logic that's employed depends on the device context's current drawing mode, which you can change with CDC::SetROP2 (short for "Set Raster Operation To"). The default drawing mode is R2_COPYPEN, which does, in fact, copy pixels to the display surface. But there are 15 other drawing modes to choose from, as shown in the table below. Together, these drawing modes represent all the possible operations that can be performed by combining the Boolean primitives AND, OR, XOR, and NOT.

Why would you ever need to change the drawing mode? Suppose you want to draw a line not by copying pixels to the display surface but by inverting the colors of the pixels already there. It's easy to do; you just set the drawing mode to R2_NOT before drawing the line:

dc.SetROP2 (R2_NOT); dc.MoveTo (0, 0); dc.LineTo (100, 100); 

This little trick might be more useful than you think, because it's a great way to rubber-band lines and rectangles. You'll see an example of what I mean in Chapter 3.

GDI Drawing Modes

Drawing Mode Operation(s) Performed
R2_NOP dest = dest
R2_NOT dest = NOT dest
R2_BLACK dest = BLACK
R2_WHITE dest = WHITE
R2_COPYPEN dest = src
R2_NOTCOPYPEN dest = NOT src
R2_MERGEPENNOT dest = (NOT dest) OR src
R2_MASKPENNOT dest = (NOT dest) AND src
R2_MERGENOTPEN dest = (NOT src) OR dest
R2_MASKNOTPEN dest = (NOT src) AND dest
R2_MERGEPEN dest = dest OR src
R2_NOTMERGEPEN dest = NOT (dest OR src)
R2_MASKPEN dest = dest AND src
R2_NOTMASKPEN dest = NOT (dest AND src)
R2_XORPEN dest = src XOR dest
R2_NOTXORPEN dest = NOT (src XOR dest)


The Mapping Mode

Without a doubt, the aspect of GDI programming that new Windows programmers find the most confusing is the mapping mode. Simply put, the mapping mode is the attribute of the device context that governs how logical coordinates are translated into device coordinates. Logical coordinates are the coordinates you pass to CDC output functions. Device coordinates are the corresponding pixel positions within a window. When you call the Rectangle function like this:

dc.Rectangle (0, 0, 200, 100); 

you're not necessarily telling the GDI to draw a rectangle that's 200 pixels wide and 100 pixels tall; you're telling it to draw a rectangle that's 200 units wide and 100 units tall. In the default mapping mode, MM_TEXT, it just so happens that 1 unit equals 1 pixel. But in other mapping modes, logical units are translated into device units differently. In the MM_LOENGLISH mapping mode, for example, 1 unit equals 1/100 of an inch. Therefore, drawing a rectangle that measures 200 units by 100 units in the MM_LOENGLISH mapping mode produces a 2-inch by 1-inch rectangle. Using a non-MM_TEXT mapping mode is a convenient way to scale your output so that sizes and distances are independent of the output device's physical resolution.

Windows supports eight different mapping modes. Their properties are summarized in the following table.

GDI Mapping Modes

Mapping Mode Distance Corresponding to One Logical Unit Orientation of the x and y Axes
MM_TEXT 1 pixel
MM_LOMETRIC 0.1 mm
MM_HIMETRIC 0.01 mm
MM_LOENGLISH 0.01 in.
MM_HIENGLISH 0.001 in.
MM_TWIPS 1/1440 in. (0.0007 in.)
MM_ISOTROPIC User-defined (x and y scale identically) User-defined
MM_ANISOTROPIC User-defined (x and y scale independently) User-defined

When you draw in the MM_TEXT mapping mode, you're using the coordinate system shown in Figure 2-1. The origin is in the upper left corner of the window, the positive x axis points to the right, the positive y axis points downward, and 1 unit equals 1 pixel. If you switch to one of the "metric" mapping modes—MM_LOENGLISH, MM_HIENGLISH, MM_LOMETRIC, MM_HIMETRIC, or MM_TWIPS—the y axis flips so that positive y points upward and logical units are scaled to represent real distances rather than raw pixel counts. The origin, however, remains in the upper left corner. One thing to remember when using a metric mapping mode is that you must use negative y values if you want to see your output. The statement

dc.Rectangle (0, 0, 200, 100); 

draws a 200-pixel by 100-pixel rectangle in the MM_TEXT mapping mode. The same statement produces no output in the MM_LOENGLISH mapping mode because positive y coordinates lie outside the visible part of the window. To make the rectangle visible, you must negate the y coordinates, as shown here:

dc.Rectangle (0, 0, 200, -100); 

If you switch to a non-MM_TEXT mapping mode and suddenly your application's output is no longer visible, check the sign of your y coordinates. Positive y coordinates will be the problem almost every time.

 

Figure 2-1. The MM_TEXT coordinate system.

The default mapping mode is MM_TEXT. If you want to use one of the other mapping modes, you must call CDC::SetMapMode to change the mapping mode. The following statements switch to the MM_LOMETRIC mapping mode and draw an ellipse whose major axis is 5 centimeters long and whose minor axis measures 3 centimeters:

dc.SetMapMode (MM_LOMETRIC); dc.Ellipse (0, 0, 500, -300); 

You can see that there's really nothing tricky about mapping modes. Things get slightly more complicated when you use the MM_ISOTROPIC and MM_ANISOTROPIC modes and when you do hit-testing on objects drawn in non-MM_TEXT mapping modes, but even that doesn't have to be difficult. The MM_ISOTROPIC and MM_ANISOTROPIC mapping modes are discussed in the next section.

One thing to keep in mind when you use the metric mapping modes is that on display screens, 1 logical inch usually doesn't equal 1 physical inch. In other words, if you draw a line that's 100 units long in the MM_LOENGLISH mapping mode, the line probably won't be exactly 1 inch long. The reason? Windows doesn't know the physical resolution of your monitor—the number of dots per inch (dpi) it's capable of displaying horizontally and vertically. (This might change in a future version of Windows.) The same is not true of printers and other hardcopy devices, however. The printer driver knows that a 600 dpi laser printer can print exactly 600 dots per inch, so a 100-unit line drawn in the MM_LOENGLISH mapping mode will measure exactly 1 inch on the printed page.

Programmable Mapping Modes

The MM_ISOTROPIC and MM_ANISOTROPIC mapping modes differ from the other mapping modes in one important respect: It's you, not Windows, who determines how logical coordinates are converted into device coordinates. For this reason, these mapping modes are sometimes called the "roll-your-own" or "programmable" mapping modes. Want a mapping mode in which 1 unit equals 1 centimeter? No problem: Just use the MM_ANISOTROPIC mapping mode and set its scaling parameters accordingly.

The most common use for the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes is for drawing output that automatically scales to match the window size. The following code fragment uses the MM_ANISOTROPIC mapping mode to draw an ellipse that touches all four borders of the window in which it is drawn:

CRect rect; GetClientRect (&rect); dc.SetMapMode (MM_ANISOTROPIC); dc.SetWindowExt (500, 500); dc.SetViewportExt (rect.Width (), rect.Height ()); dc.Ellipse (0, 0, 500, 500); 

See how it works? No matter what physical size the window is, you've told Windows that the window's logical size is 500 units by 500 units. Therefore, a bounding box that stretches from (0,0) to (500,500) encompasses the entire window. Initializing a device context in this way places the origin at the upper left corner of the window and orients the axes so that positive x points to the right and positive y points downward. If you'd rather have the y axis point upward (as it does in the metric mapping modes), you can reverse its direction by negating the y value passed to either SetWindowExt or SetViewportExt:

CRect rect; GetClientRect (&rect); dc.SetMapMode (MM_ANISOTROPIC); dc.SetWindowExt (500, -500); dc.SetViewportExt (rect.Width (), rect.Height ()); dc.Ellipse (0, 0, 500, -500); 

Now you must use negative y coordinates to draw in the window. Only the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes allow the directions of the x and y axes to be reversed. That's why the table in the previous section listed these two mapping modes' axis orientations as user defined.

The only difference between the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes is that in the former, the scaling factors for the x and y directions are always the same. In other words, 100 horizontal units equals the same physical distance as 100 vertical units. Isotropic means "equal in all directions." The MM_ISOTROPIC mapping mode is ideal for drawing circles and squares. The following code draws a circle that spans the width or height of a window, whichever is smaller:

CRect rect; GetClientRect (&rect); dc.SetMapMode (MM_ISOTROPIC); dc.SetWindowExt (500, 500); dc.SetViewportExt (rect.Width (), rect.Height ()); dc.Ellipse (0, 0, 500, 500); 

As far as Windows is concerned, the window's logical size is once again 500 units by 500 units. But now the GDI takes the output device's aspect ratio into consideration when converting logical units to device units. Chapter 14's Clock program uses the MM_ISOTROPIC mapping mode to draw a round clock face and to automatically scale the clock size to the window size. Without the MM_ISOTROPIC mapping mode, Clock would have to do all of the scaling manually.

Let's talk a bit about the SetWindowExt and SetViewportExt functions. Officially, SetWindowExt sets the "window extents" and SetViewportExt sets the "viewport extents." Think of a window as something whose size is measured in logical units and a viewport as something whose size is measured in device units, or pixels. When Windows converts between logical coordinates and device coordinates, it uses a pair of formulas that factor in the window's logical dimensions (the window extents) and its physical dimensions (the viewport extents) as well as the location of the origin. When you set the window extents and viewport extents, you're effectively programming in your own scaling parameters. Generally, the viewport extents are simply the size (in pixels) of the window you're drawing in and the window extents are the window's desired size in logical units.

One caveat regarding the use of SetWindowExt and SetViewportExt is that in the MM_ISOTROPIC mapping mode, you should call SetWindowExt first. Otherwise, a portion of the window's client area might fall outside the window's logical extents and become unusable. In the MM_ANISOTROPIC mapping mode, it doesn't matter which are set first—the window extents or the viewport extents.

Coordinate Conversions

You can translate logical coordinates to device coordinates using the CDC::LPtoDP function. Conversely, you can translate device coordinates to logical coordinates with CDC::DPtoLP.

Let's say you want to know where the center of a window is in device coordinates. All you have to do is halve the window's pixel width and height. CWnd::GetClientRect returns a window's pixel dimensions.

CRect rect; GetClientRect (&rect); CPoint point (rect.Width () / 2, rect.Height () / 2); 

If you want to know where the center point is in MM_LOENGLISH units, however, you need DPtoLP:

CRect rect; GetClientRect (&rect); CPoint point (rect.Width () / 2, rect.Height () / 2); CClientDC dc (this); dc.SetMapMode (MM_LOENGLISH); dc.DPtoLP (&point); 

When DPtoLP returns, point holds the coordinates of the center point in logical (that is, MM_LOENGLISH) coordinates. If, on the other hand, you want to know the pixel coordinates of the point whose MM_LOENGLISH coordinates are (100,100), you use LPtoDP:

CPoint point (100, 100); CClientDC dc (this); dc.SetMapMode (MM_LOENGLISH); dc.LPtoDP (&point); 

One situation in which LPtoDP and DPtoLP are indispensable is when you're performing hit-testing in response to mouse clicks. Mouse clicks are always reported in device coordinates, so if you've drawn a rectangle in MM_LOENGLISH coordinates and you want to know whether a mouse click occurred inside that rectangle, you must either convert the rectangle's coordinates to device coordinates or convert the click coordinates to logical coordinates. Otherwise, you'll be comparing apples and oranges.

Moving the Origin

By default, a device context's origin is in the upper left corner of the display surface. Even if you change the mapping mode, the origin remains in the upper left corner. But just as you can change the mapping mode, you can also move the origin. MFC's CDC class provides two functions for moving the origin. CDC::SetWindowOrg moves the window origin, and CDC::SetViewportOrg moves the viewport origin. You'll normally use one but not both. Using both can be very confusing.

Suppose you'd like to move the origin to the center of the window so that you can center what you draw by centering your output around the point (0,0). Assuming that dc is a device context object, here's one way to do it:

CRect rect; GetClientRect (&rect); dc.SetViewportOrg (rect.Width () / 2, rect.Height () / 2); 

Here's another way to accomplish the same thing, assuming that you're working in the MM_LOENGLISH mapping mode:

CRect rect; GetClientRect (&rect); CPoint point (rect.Width () / 2, rect.Height () / 2); dc.SetMapMode (MM_LOENGLISH); dc.DPtoLP (&point); dc.SetWindowOrg (-point.x, -point.y); 

It's easy to get SetViewportOrg and SetWindowOrg confused, but the distinction between them is actually quite clear. Changing the viewport origin to (x,y) with SetViewportOrg tells Windows to map the logical point (0,0) to the device point (x,y). Changing the window origin to (x,y) with SetWindowOrg does essentially the reverse, telling Windows to map the logical point (x,y) to the device point (0,0)—the upper left corner of the display surface. In the MM_TEXT mapping mode, the only real difference between the two functions is the signs of x and y. In other mapping modes, there's more to it than that because SetViewportOrg deals in device coordinates and SetWindowOrg deals in logical coordinates. You'll see examples of how both functions are used later in this chapter.

As a final example, suppose you're drawing in the MM_HIMETRIC mapping mode, where 1 unit equals 1/100 of a millimeter, positive x points to the right, and positive y points upward, and you'd like to move the origin to the lower left corner of the window. Here's an easy way to do it:

CRect rect; GetClientRect (&rect); dc.SetViewportOrg (0, rect.Height ()); 

Now you can draw with positive x and y values using coordinates relative to the window's lower left corner.

A Final Word on Coordinate Systems

When you talk about mapping modes, window origins, viewport origins, and other idioms related to the GDI's handling of coordinates, it's easy to get tangled up in the terminology. Understanding the difference between the device coordinate system and the logical coordinate system might help clear some of the cobwebs.

In the device coordinate system, distances are measured in pixels. The device point (0,0) is always in the upper left corner of the display surface, and the positive x and y axes always point right and downward. The logical coordinate system is altogether different. The origin can be placed anywhere, and both the orientation of the x and y axes and the scaling factor (the number of pixels that correspond to 1 logical unit) vary with the mapping mode. To be precise, they vary with the window extents and the viewport extents. You can change these extents in the MM_ISOTROPIC and MM_ANISOTROPIC mapping modes but not in the other mapping modes.

You'll sometimes hear Windows programmers talk about "client coordinates" and "screen coordinates." Client coordinates are simply device coordinates relative to the upper left corner of a window's client area. Screen coordinates are device coordinates relative to the upper left corner of the screen. You can convert from client coordinates to screen coordinates and vice versa using the CWnd::ClientToScreen and CWnd::ScreenToClient functions. Why these functions are useful will become apparent to you the first time you call a Windows function that returns screen coordinates and you pass them to a function that requires client coordinates, or vice versa.

Getting Information About a Device

Sometimes it's helpful to get information about a device before you send output to it. The CDC::GetDeviceCaps function lets you retrieve all kinds of information about a device, from the number of colors it supports to the number of pixels it can display horizontally and vertically. The following code initializes cx and cy to the width and height of the screen, in pixels:

CClientDC dc (this); int cx = dc.GetDeviceCaps (HORZRES); int cy = dc.GetDeviceCaps (VERTRES); 

If the screen resolution is 1,024 by 768, cx and cy will be set to 1,024 and 768, respectively.

The table below lists some of the parameters you can pass to GetDeviceCaps to acquire information about the physical output device associated with a device context. How you interpret the results depends somewhat on the device type. For example, calling GetDeviceCaps with a HORZRES parameter for a screen DC returns the screen width in pixels. Make the same call to a printer DC and you get back the width of the printable page, once more in pixels. As a rule, values that imply any kind of scaling (for example, LOGPIXELSX and LOGPIXELSY) return physically correct values for printers and other hardcopy devices but not for screens. For a 600 dpi laser printer, both LOGPIXELSX and LOGPIXELSY return 600. For a screen, both will probably return 96, regardless of the physical screen size or resolution.

Interpreting the color information returned by the NUMCOLORS, BITSPIXEL, and PLANES parameters of GetDeviceCaps is a bit tricky. For a printer or a plotter, you can usually find out how many colors the device is capable of displaying from the NUMCOLORS parameter. For a monochrome printer, NUMCOLORS returns 2.

Useful GetDeviceCaps Parameters

Parameter Returns
HORZRES Width of the display surface in pixels
VERTRES Height of the display surface in pixels
HORZSIZE Width of the display surface in millimeters
VERTSIZE Height of the display surface in millimeters
LOGPIXELSX Number of pixels per logical inch horizontally
LOGPIXELSY Number of pixels per logical inch vertically
NUMCOLORS For a display device, the number of static colors; for a printer or plotter, the number of colors supported
BITSPIXEL Number of bits per pixel
PLANES Number of bit planes
RASTERCAPS Bit flags detailing certain characteristics of the device, such as whether it is palettized and whether it can display bitmapped images
TECHNOLOGY Bit flags identifying the device type—screen, printer, plotter, and so on

However, the color resolution of the screen (the number of colors that can be displayed onscreen simultaneously) is computed by multiplying BITSPIXEL and PLANES and raising 2 to the power of the result, as demonstrated here:

CClientDC dc (this); int nPlanes = dc.GetDeviceCaps (PLANES); int nBPP = dc.GetDeviceCaps (BITSPIXEL); int nColors = 1 << (nPlanes * nBPP); 

If this code is executed on a PC equipped with a 256-color video adapter, nColors equals 256. Calling GetDeviceCaps with a NUMCOLORS parameter, meanwhile, returns not 256 but 20—the number of "static colors" that Windows programs into the video adapter's color palette. I'll have more to say about the color characteristics of screens and video adapters and also about static colors in Chapter 15.

I'll use GetDeviceCaps several times in this book to adapt the sample programs' output to the physical attributes of the output device. The first use will come later in this chapter, when the screen's LOGPIXELSX and LOGPIXELSY parameters are used to draw rectangles 1 logical inch long and 1/4 logical inch tall in the MM_TEXT mapping mode.

posted @ 2011-01-11 01:42 snddman 阅读(133) 评论(0) 编辑

If you've been around PCs for a while, you probably remember what graphics programming was like before Microsoft Windows came along. If you were lucky, you had a decent graphics library with routines like DrawLine and DrawCircle to draw graphics primitives for you. If you weren't so lucky, you probably spent a lot of time writing your own output routines and tweaking them to shave off a few microseconds here and there. And whether it was your code or someone else's doing the drawing, you knew that when a new graphics standard emerged—in those days, that meant whenever IBM introduced a new graphics adapter like the EGA or the VGA—you'd be scrambling to support the latest hardware. That invariably meant buying an updated version of the graphics library, adding new code to your own routines, or writing a driver for the new video card. For the graphics programmer, the platform was a moving target that never seemed to stand still for very long. And even if you did manage to draw a bead on the video hardware, you still had plenty of work to do to adapt your code to work with printers and other output devices.

Windows changed all that by bringing to the PC platform something it sorely needed: a device-independent graphics output model. In Windows, the graphics code you write will work on any video adapter for which a Windows driver is available. These days, that's just about every adapter on the planet. And to a large extent, the same code that sends output to the screen will also work with printers and other hardcopy devices. This one-size-fits-all approach to graphics programming has a number of advantages, chief among them the fact that programmers can now spend their time developing code for their applications rather than code for the hardware their applications will run on. Moreover, you no longer need third-party graphics libraries in order to do your work because Windows provides a wide assortment of graphics API functions that do everything from draw lines to create complex clipping regions that serve as stencils for other output routines.

The part of Windows responsible for graphics output is the Graphics Device Interface, or GDI. The GDI provides a number of services that an application can call. Together, these services constitute a powerful and robust graphics programming language whose richness rivals that of some third-party graphics libraries. MFC works on top of the graphics API and codifies the interface with C++ classes that represent the various components of the Windows GDI.

Now that you know how to create a window, it's time to do something with that window. The Hello application in Chapter 1 used CDC::DrawText to output text to a window. DrawText is just one of many member functions that the CDC class provides for text and graphics output. This chapter looks at the CDC class and its derivative classes in more detail and introduces three of the most commonly used GDI primitives: pens, brushes, and fonts. It also demonstrates how to add scroll bars to a window.

posted @ 2011-01-11 01:41 snddman 阅读(39) 评论(0) 编辑
摘要: It's time to build your first MFC application. And what better place to start than with one that displays "Hello, MFC" in a window? Based on the classic "Hello, world" program immortalized in Brian Kernighan and Dennis Ritchie's The C Programming Language (1988, Prentice-Hall), this very minimal 阅读全文
posted @ 2011-01-11 01:40 snddman 阅读(109) 评论(0) 编辑

MFC is the C++ class library Microsoft provides to place an object-oriented wrapper around the Windows API. Version 6 contains about 200 classes, some of which you'll use directly and others of which will serve primarily as base classes for classes of your own. Some MFC classes are exceedingly simple, such as the CPoint class that represents a point (a location defined by x and y coordinates). Others are more complex, such as the CWnd class that encapsulates the functionality of a window. In an MFC program, you don't often call the Windows API directly. Instead, you create objects from MFC classes and call member functions belonging to those objects. Many of the hundreds of member functions defined in the class library are thin wrappers around the Windows API and even have the same names as the corresponding API functions. An obvious benefit of this naming convention is that it speeds the transition for C programmers making the move to MFC. Want to move a window? A C programmer would probably call the SetWindowPos API function. Look up SetWindowPos in an MFC reference, and you'll find that MFC supports SetWindowPos, too. It's a member of the CWnd class, which makes sense when you think of a window as an object and SetWindowPos as an operation you might want to perform on that object.

MFC is also an application framework. More than merely a collection of classes, MFC helps define the structure of an application and handles many routine chores on the application's behalf. Starting with CWinApp, the class that represents the application itself, MFC encapsulates virtually every aspect of a program's operation. The framework supplies the WinMain function, and WinMain in turn calls the application object's member functions to make the program go. One of the CWinApp member functions called by WinMainRun—provides the message loop that pumps messages to the application's window. The framework also provides abstractions that go above and beyond what the Windows API has to offer. For example, MFC's document/view architecture builds a powerful infrastructure on top of the API that separates a program's data from graphical representations, or views, of that data. Such abstractions are totally foreign to the API and don't exist outside the framework of MFC or a similar class library.

The Benefits of Using C++ and MFC

The fact that you're reading this book means you've probably already heard the traditional arguments in favor of using an object-oriented design methodology: reusability, tighter binding of code and data, and so on. And you should already be familiar with common object-oriented programming (OOP) terms such as object, inheritance, and encapsulation, particularly as they pertain to the C++ language. But without a good class library to serve as a starting point, OOP does little to reduce the amount of code you write.

That's where MFC comes in. Want to add a toolbar to your application—one that can be docked to different sides of a window or floated in a window of its own? No problem: MFC provides a CToolBar class that does the bulk of the work for you. Need a linked list or a resizeable array? That's easy, too: CList, CArray, and other MFC collection classes provide canned containers for your data. And don't forget about COM, OLE, and ActiveX. Few among us have the desire or the know-how to write an ActiveX control from scratch. MFC simplifies the development of ActiveX controls by providing the bulk of the code you need in classes such as COleControl and COlePropertyPage.

Another advantage to using MFC is that the framework uses a lot of tricks to make Windows objects such as windows, dialog boxes, and controls behave like C++ objects. Suppose you want to write a reusable list box class that displays a navigable list of drives and directories on the host PC. Unless you create a custom control to do the job, you can't implement such a list box in C because clicking an item in the list box sends a notification to the list box's parent (the window or the dialog box in which the list box appears), and it's up to the parent to process that notification. In other words, the list box control doesn't control its own destiny; it's the parent's job to update the list box's contents when a drive or a directory is changed.

Not so with MFC. In an MFC application, windows and dialog boxes reflect unprocessed notifications back to the controls that sent them. You can create a self-contained and highly reusable list box class that responds to its own click notifications by deriving your own list box class from CListBox. The resulting list box implements its own behavior and can be ported to another application with little more than a #include statement in a source code file. That's what reusability is all about.

The MFC Design Philosophy

When the programmers at Microsoft set out to create MFC, they had a vision of the future that included a pair of key design goals:

  • MFC should provide an object-oriented interface to the Windows operating system that supports reusability, self-containment, and other tenets of OOP.
  • It should do so without imposing undue overhead on the system or unnecessarily adding to an application's memory requirements.

The first goal was accomplished by writing classes to encapsulate windows, dialog boxes, and other objects and by including key virtual functions that can be overridden to alter the behavior of derived classes. The second goal required the architects of MFC to make some choices early on about how windows, menus, and other objects would be wrapped by MFC classes such as CWnd and CMenu. Efficient use of memory was important then and it's important today, because nobody likes a class library that produces bloated code.

One of the ways in which the designers of MFC minimized the overhead added by the class library is manifested in the relationship between MFC objects and Windows objects. In Windows, information about the characteristics and current state of a window is stored in memory owned by the operating system. This information is hidden from applications, which deal exclusively with window handles, or HWNDs. Rather than duplicate all the information associated with an HWND in the data members of the CWnd class, MFC wraps a window in a CWnd by storing the HWND in a public CWnd data member named m_hWnd. As a rule, if Windows exposes an object through a handle of some type, the corresponding MFC class will contain a data member for that handle. This knowledge can be useful if you want to call an API function that requires a handle but you have, say, a CWnd or CWnd pointer instead of an HWND.

The Document/View Architecture

The cornerstone of MFC's application framework is the document/view architecture, which defines a program structure that relies on document objects to hold an application's data and on view objects to render views of that data. MFC provides the infrastructure for documents and views in the classes CDocument and CView. CWinApp , CFrameWnd, and other classes work in conjunction with CDocument and CView to bind all the pieces together. It's a little early to discuss the details of the document/view architecture, but you should at least be familiar with the term document/view because it inevitably comes up in any discussion of MFC.

The reason documents and views are so important is that document/view applications derive the greatest benefit from the application framework. You can write MFC programs that don't use documents and views (and we'll do a lot of that in this book, especially in Chapters 1 through 8), but to get the most out of the framework and take advantage of some of MFC's most advanced features, you must use the document/view architecture. That's not as restricting as it sounds, because almost any program that relies on documents of some type can be cast in the document/view mold. Don't let the term document mislead you into thinking that the document/view architecture is useful only for writing word processors and spreadsheet programs. A document is simply an abstract representation of a program's data. A document could just as easily be a byte array that stores board positions in a computerized game of chess as it could be a spreadsheet.

What kinds of support does MFC provide to document/view applications? Among other things, the document/view architecture vastly simplifies printing and print previewing, the mechanics of saving documents to disk and reading them back again, and converting applications into Active document servers whose documents can be opened in Microsoft Internet Explorer. You'll learn all about the document/view architecture in Part II of this book, but only after you've done some programming without documents and views so that you can get to know MFC without having too much heaped on your plate at once.

The MFC Class Hierarchy

MFC provides a variety of classes designed to serve a wide range of needs. You'll find a handy diagram of the MFC 6.0 class hierarchy inside the front cover of this book.

The majority of MFC classes are derived, either directly or indirectly, from CObject. CObject provides three important features to classes that inherit from it:

  • Serialization support
  • Run-time class information support
  • Diagnostic and debugging support

Serialization is the process of streaming an object's persistent data to or from a storage medium such as a disk file. By using CObject as a base class, you can write serializable classes whose instances are easily saved and re-created. Run-time class information (RTCI) lets you retrieve an object's class name and other information about the object at run time. RTCI is implemented apart from the run-time type information (RTTI) mechanism in C++ because it predated RTTI by a number of years. Diagnostic and debugging support built into CObject let you perform validity checks on instances of CObject-derived classes and dump state information to a debugging window.

CObject provides other benefits to its derived classes as well. For example, it overloads the new and delete operators to provide protection against memory leaks. If you create an object from a CObject-derived class and fail to delete it before the application terminates, MFC will warn you by writing a message to the debug output window. The overarching importance of this most basic of MFC classes will become increasingly clear as you grow more familiar with MFC.

AFX Functions

Not all of the functions that MFC offers are members of classes. MFC provides an API of sorts all its own in the form of global functions whose names begin with Afx. Class member functions can be called only in the context of the objects to which they belong, but AFX functions are available anytime and anywhere.

The following table lists some of the more commonly used AFX functions. AfxBeginThread simplifies the process of creating threads of execution. AfxMessageBox is the global equivalent of the Windows MessageBox function and, unlike CWnd::MessageBox, can be called just as easily from a document class as from a window class. AfxGetApp and AfxGetMainWnd return pointers to the application object and the application's main window and are useful when you want to access a function or data member of those objects but don't have a pointer readily available. AfxGetInstanceHandle is handy when you need an instance handle to pass to a Windows API function. (Even MFC programs call API functions every now and then!)

Commonly Used AFX Functions

Function Name Description
AfxAbort Unconditionally terminates an application; usually called when an unrecoverable error occurs
AfxBeginThread Creates a new thread and begins executing it
AfxEndThread Terminates the thread that is currently executing
AfxMessageBox Displays a Windows message box
AfxGetApp Returns a pointer to the application object
AfxGetAppName Returns the name of the application
AfxGetMainWnd Returns a pointer to the application's main window
AfxGetInstanceHandle Returns a handle identifying the current application instance
AfxRegisterWndClass Registers a custom WNDCLASS for an MFC application

posted @ 2011-01-11 01:36 snddman 阅读(57) 评论(0) 编辑

Programs written for traditional operating environments use a procedural programming model in which programs execute from top to bottom in an orderly fashion. The path taken from start to finish may vary with each invocation of the program depending on the input it receives or the conditions under which it is run, but the path remains fairly predictable. In a C program, execution begins with the first line in the function named main and ends when main returns. In between, main might call other functions and these functions might call even more functions, but ultimately it is the program—not the operating system—that determines what gets called and when.

Windows programs operate differently. They use the event-driven programming model illustrated in Figure 1-1, in which applications respond to events by processing messages sent by the operating system. An event could be a keystroke, a mouse click, or a command for a window to repaint itself, among other things. The entry point for a Windows program is a function named WinMain, but most of the action takes place in a function known as the window procedure. The window procedure processes messages sent to the window. WinMain creates that window and then enters a message loop, alternately retrieving messages and dispatching them to the window procedure. Messages wait in a message queue until they are retrieved. A typical Windows application performs the bulk of its processing in response to the messages it receives, and in between messages, it does little except wait for the next message to arrive.

The message loop ends when a WM_QUIT message is retrieved from the message queue, signaling that it's time for the application to end. This message usually appears because the user selected Exit from the File menu, clicked the close button (the small button with an X in the window's upper right corner), or selected Close from the window's system menu. When the message loop ends, WinMain returns and the application terminates.

Figure 1-1. The Windows programming model.

The window procedure typically calls other functions to help process the messages it receives. It can call functions local to the application, or it can call API functions provided by Windows. API functions are contained in special modules known as dynamic-link libraries, or DLLs. The Win32 API includes hundreds of functions that an application can call to perform various tasks such as creating a window, drawing a line, and performing file input and output. In C, the window procedure is typically implemented as a monolithic function containing a large switch statement with cases for individual messages. The code provided to process a particular message is known as a message handler. Messages that an application doesn't process are passed on to an API function named DefWindowProc, which provides default responses to unprocessed messages.

Messages, Messages, and More Messages

Where do messages come from, and what kinds of information do they convey? Windows defines hundreds of different message types. Most messages have names that begin with the letters "WM" and an underscore, as in WM_CREATE and WM_PAINT. These messages can be classified in various ways, but for the moment classification is not nearly as important as realizing the critical role messages play in the operation of an application. The following table shows 10 of the most common messages. A window receives a WM_PAINT message, for example, when its interior needs repainting. One way to characterize a Windows program is to think of it as a collection of message handlers. To a large extent, it is a program's unique way of responding to messages that gives it its personality.

Common Windows Messages

Message Sent When
WM_CHAR A character is input from the keyboard.
WM_COMMAND The user selects an item from a menu, or a control sends a notification to its parent.
WM_CREATE A window is created.
WM_DESTROY A window is destroyed.
WM_LBUTTONDOWN The left mouse button is pressed.
WM_LBUTTONUP The left mouse button is released.
WM_MOUSEMOVE The mouse pointer is moved.
WM_PAINT A window needs repainting.
WM_QUIT The application is about to terminate.
WM_SIZE A window is resized.

A message manifests itself in the form of a call to a window's window procedure. Bundled with the call are four input parameters: the handle of the window to which the message is directed, a message ID, and two 32-bit parameters known as wParam and lParam. The window handle is a 32-bit value that uniquely identifies a window. Internally, the value references a data structure in which Windows stores relevant information about the window such as its size, style, and location on the screen. The message ID is a numeric value that identifies the message type: WM_CREATE, WM_PAINT, and so on. wParam and lParam contain information specific to the message type. When a WM_LBUTTONDOWN message arrives, for example, wParam holds a series of bit flags identifying the state of the Ctrl and Shift keys and of the mouse buttons. lParam holds two 16-bit values identifying the location of the mouse pointer when the click occurred. Together, these parameters provide the window procedure with all the information it needs to process the WM_LBUTTONDOWN message.

Windows Programming, SDK-Style

If you haven't programmed Windows in C before, it's instructive to see what the source code for a simple program looks like. The program listed in Figure 1-2 creates a window and responds to WM_PAINT messages by drawing an ellipse in the window's upper left corner. This code is similar to the source code you'll find in books such as Charles Petzold's Programming Windows (1998, Microsoft Press) and other books that teach Windows programming in C.

Figure 1-2. C source code for a simple Windows program.

#include <windows.h>

LONG WINAPI WndProc (HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style = 0;                                   // Class style
    wc.lpfnWndProc = (WNDPROC) WndProc;             // Window procedure address
    wc.cbClsExtra = 0;                              // Class extra bytes
    wc.cbWndExtra = 0;                              // Window extra bytes
    wc.hInstance = hInstance;                       // Instance handle
    wc.hIcon = LoadIcon (NULL, IDI_WINLOGO);        // Icon handle
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);      // Cursor handle
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // Background color
    wc.lpszMenuName = NULL;                         // Menu name
    wc.lpszClassName = "MyWndClass";                // WNDCLASS name

    RegisterClass (&wc);
    

    hwnd = CreateWindow (
        "MyWndClass",               // WNDCLASS name
        "SDK Application",          // Window title
        WS_OVERLAPPEDWINDOW,        // Window style
        CW_USEDEFAULT,              // Horizontal position
        CW_USEDEFAULT,              // Vertical position        
        CW_USEDEFAULT,              // Initial width
        CW_USEDEFAULT,              // Initial height
        HWND_DESKTOP,               // Handle of parent window
        NULL,                       // Menu handle
        hInstance,                  // Application's instance handle
        NULL                        // Window-creation data
    );

    ShowWindow (hwnd, nCmdShow);
    UpdateWindow (hwnd);

    while (GetMessage (&msg, NULL, 0, 0)) {
        TranslateMessage (&msg);
        DispatchMessage (&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,
    LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message) {
    
    case WM_PAINT:
        hdc = BeginPaint (hwnd, &ps);
        Ellipse (hdc, 0, 0, 200, 100);
        EndPaint (hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage (0);
        return 0;
    }
    return DefWindowProc (hwnd, message, wParam, lParam);
}

WinMain begins by calling the API function RegisterClass to register a window class. The window class defines important characteristics of a window such as its window procedure address, its default background color, and its icon. These and other properties are defined by filling in the fields of a WNDCLASS structure, which is subsequently passed to RegisterClass. An application must specify a window class when it creates a window, and a class must be registered before it can be used. That's why RegisterClass is called at the outset of the program. Keep in mind that a WNDCLASS-type window class is not the same as a C++ window class. To avoid confusion, I'll use the term WNDCLASS throughout this book to refer to classes registered with RegisterClass. The term window class will refer to C++ classes derived from MFC's CWnd class.

Once the WNDCLASS is registered, WinMain calls the all-important CreateWindow function to create the application's window. The first parameter to CreateWindow is the name of the WNDCLASS from which the window will be created. The second parameter is the text that will appear in the window's title bar. The third specifies the window style. WS_OVERLAPPEDWINDOW is a commonly used style that creates a top-level window with a resizing border, a title bar, a system menu, and buttons for minimizing, maximizing, and closing the window.

The next four parameters specify the window's initial position and size. CW_USEDEFAULT tells Windows to use default values for both. The final four parameters specify, in order, the handle of the window's parent window (HWND_DESKTOP for an application's main window); the handle of the menu associated with the window, if any; the application's instance handle (a value that lets the programmer differentiate between the program itself and the modules—that is, DLLs—that it loads); and a pointer to application-specific window-creation data. I could easily devote a section of this book to CreateWindow and its parameters, but as you'll see later, MFC hides much of this detail inside the class library. A typical MFC application doesn't have a WinMain function (at least not one you can see), and it doesn't call RegisterClass or CreateWindow.

The window that CreateWindow creates is not initially visible on the screen because it was not created with the WS_VISIBLE style. (Had it been used, WS_VISIBLE would have been combined with WS_OVERLAPPEDWINDOW in the call to CreateWindow.) Therefore, WinMain follows CreateWindow with calls to ShowWindow and UpdateWindow, which make the window visible and ensure that its WM_PAINT handler is called immediately.

Next comes the message loop. In order to retrieve and dispatch messages, WinMain executes a simple while loop that calls the GetMessage, TranslateMessage, and DispatchMessage API functions repeatedly. GetMessage checks the message queue. If a message is available, it is removed from the queue and copied to msg; otherwise, GetMessage blocks on the empty message queue until a message is available. msg is an instance of the structure MSG, whose fields contain pertinent message parameters such as the message ID and the time at which the message was placed in the queue. TranslateMessage converts a keyboard message denoting a character key to an easier-to-use WM_CHAR message, and DispatchMessage dispatches the message to the window procedure. The message loop executes until GetMessage returns 0, which happens only when a WM_QUIT message is retrieved from the message queue. When this occurs, WinMain ends and the program terminates.

Messages dispatched with DispatchMessage generate calls to the window procedure WndProc. The sample program in Figure 1-2 processes just two message types, WM_PAINT and WM_DESTROY; all other messages are passed to DefWindowProc for default processing. A switch-case block inspects the message ID passed in the message parameter and executes the appropriate message handler. The WM_PAINT handler calls the BeginPaint API function to obtain a device context handle before painting begins and the EndPaint API function to release the handle when painting is finished. In between, the Ellipse API function draws an ellipse that is 200 pixels wide and 100 pixels high. A device context handle is the "magic cookie" that permits a Windows application to draw on the screen. Without it, functions such as Ellipse won't work.

The WM_DESTROY handler calls the PostQuitMessage API function to post a WM_QUIT message to the message queue and ultimately cause the program to terminate. The WM_DESTROY message is sent to a window just before it is destroyed. A top-level window must call PostQuitMessage when it receives a WM_DESTROY message, or else the message loop will not fall through and the program will never end.

Hungarian Notation and Windows Data Types

Another aspect of Figure 1-2 that deserves mentioning is the variable naming convention that it uses. Veteran Windows programmers know it as Hungarian notation, in which each variable name begins with one or more lowercase characters identifying the variable's type: h for handle, n for integer, and so on. The table below lists some of the commonly used Hungarian prefixes. Prefixes are often combined to form other prefixes, as when p and sz are joined to form psz, which stands for "pointer to zero-terminated string."

Many of the data types shown in this table aren't standard C/C++ data types but rather are "special" data types defined in the Windows header files. COLORREF, for example, is the Windows data type for 24-bit RGB color values. A BOOL is a Boolean data type that stores TRUE/FALSE values, while a DWORD is a 32-bit unsigned integer. Over time, you'll come to know these data types as well as you know your compiler's native data types.

Common Hungarian Notation Prefixes

Prefix Data Type
b BOOL
c or ch char
clr COLORREF
cx, cy Horizontal or vertical distance
dw DWORD
h Handle
l LONG
n int
p Pointer
sz Zero-terminated string
w WORD

Most MFC programmers use Hungarian notation, too. Glance through the source code for a typical MFC program and you'll see hundreds of hs and lps and other familiar prefixes as well as prefixes representing MFC's own data types (for example, wnd for CWnd variables). It's also common to prefix member variables with m_ so that it's obvious whether a variable is a member of a class. A temporary CString variable created on the stack might have the name strWndClass, but if it's a member variable it will probably be called m_strWndClass. You don't have to abide by these rules yourself, of course, but observing established naming conventions will make your code more readable to other programmers who do.

SDK Programming in Perspective

All this is a lot to digest if you've never programmed Windows before, but it brings to light a few very important concepts. First, Windows is an event-driven, message-based operating system. Messages are the key to everything that goes on in the system, and for an application, very few things happen that aren't the direct result of receiving a message. Second, there are many different API functions and many different message types, which complicates application development and makes it hard to predict all of the scenarios an application might encounter. Third, seeing how Windows programming is done the hard way provides a baseline for evaluating MFC and other class libraries. MFC is not the panacea some of its proponents would have you believe, but it undeniably makes certain aspects of Windows programming easier. And the higher order it lends to Windows programs frees programmers to spend more time developing the structural components of a program and less time worrying about the style bits passed to CreateWindow and other nuances of the API. If you haven't given MFC a look, now is the time to consider it. Windows programming isn't getting any easier, and MFC lets you benefit from tens of thousands of lines of code already written and tested by Microsoft.

 

 

posted @ 2011-01-11 01:35 snddman 阅读(53) 评论(0) 编辑

Like many of my colleagues in this industry, I learned Windows programming from Charles Petzold's Programming Windows—a classic programming text that is the bible to an entire generation of Windows programmers. When I set out to become an MFC programmer in 1994, I went shopping for an MFC equivalent to Programming Windows. After searching in vain for such a book and spending a year learning MFC the old-fashioned way, I decided to write one myself. It's the book you hold in your hands. And it's the book I would like to have had when I was learning to program Windows the MFC way.

MFC, as you probably already know, is Microsoft's C++ class library for Windows programming. Programming Windows with MFC isn't a book about C++; rather, it's a book about writing 32-bit Windows applications in C++ using MFC rather than the Windows API as the chief means of accessing the operating system's essential features and services. It was written with two kinds of people in mind:

  • Windows API programmers who want to learn MFC
  • Programmers who have never before programmed Windows

Whichever camp you fall into, I assume that you know the C++ programming language already and are comfortable with basic C++ idioms such as derived classes and virtual functions. If these assumptions are true, you're ready to begin climbing the hill that is MFC programming.

Even veteran Windows programmers frequently find MFC code confusing the first time they see it, in part because of the presence of code created by the MFC code-generating wizards in Visual C++ and in part because of the countless lines of code hidden away in MFC classes such as CFrameWnd, CDocument, and CView. That's why this book takes a rather unusual approach to teaching MFC. It begins by having you write MFC code by hand (without the wizards) and by utilizing MFC 1.0-style application architectures—that is, applications that use neither documents nor views. Only after you've mastered the fundamentals and become acquainted with basic MFC classes such as CWnd and CWinApp do I introduce the wizards and teach you how to take advantage of MFC's document/view architecture. Along the way, you build a understanding from the ground up of the message-oriented nature of Windows and of key components of Windows itself, such as the Graphics Device Interface (GDI). I believe that this approach makes learning MFC not only less intimidating, but also more enjoyable. I think that you'll agree once you've worked your way through the book and can look back on the learning experience from the standpoint of a knowledgeable Windows programmer.

Programming Windows with MFC is divided into four parts. Part I introduces the core tenets of MFC and Windows programming, beginning with a simple "Hello, MFC" application and introducing, one by one, menus, controls, dialog boxes, and other application building blocks. Part II builds on the foundation laid in Part I with a detailed look at the document/view architecture. In particular, Chapters 9, 10, and 11 reveal much of the "magic" behind documents and views and explain not only how to write basic document/view applications but also how to implement some not so basic features such as split-window views of a document and print previews. Part III covers some of the more advanced features of Windows and MFC—features such as color palettes, bitmap handling, and multiple threads of execution. In Part IV, you'll learn how MFC wraps its arms around COM, OLE, and ActiveX and how to write COM-enabled applications and software components. By the time you're finished with Chapter 21, you'll be well versed in the art of 32-bit Windows programming using MFC. And you'll have prodigious amounts of sample code to draw from when it's time to strike out on your own and write your first great Windows application.

What's New in the Second Edition

Those of you who read the first edition of this book will notice two rather obvious changes in the second edition. First, this edition contains seven new chapters. One is devoted to the MFC view classes; another covers the MFC collection classes; one introduces MFC file I/O and serialization mechanisms; and four cover the relationship between MFC and COM. MFC is not the general-purpose COM framework that the Active Template Library (ATL) is, but MFC makes certain types of COM programming exceptionally easy. For example, MFC greatly simplifies the task of writing ActiveX controls, and it makes writing Automation servers—programs that use COM to expose their functionality to scripting clients—a breeze.

The second major change in this edition has to do with wizards. The first edition didn't cover the MFC wizards at all. The second edition uses hand-generated code in Chapters 1 through 3 but then shifts gears and begins using AppWizard and ClassWizard in Chapter 4. Why the change of heart? I still believe that code-generating wizards are an impediment to learning and should be used only by knowledgeable programmers, but I've also come to realize that in the real world, MFC programmers use the wizards. For certain tasks—writing ActiveX controls, for example—it doesn't make sense not to use the wizards. So after much deliberation, I decided I would be remiss not to cover them.

Despite the new material regarding wizards, however, this is not—and never will be—a book about clicking buttons in AppWizard. After introducing a fundamental skill, such as how to write a message handler with ClassWizard, I thereafter let the source code do the talking and assume that you can figure out how the source code was created. Keep in mind that the wizards never do anything you can't do yourself, so it's perfectly feasible to type in every source code listing by hand if you'd like to.

The downside to using wizards in a book that teaches MFC programming is that they produce code that isn't fit to publish. The first edition of this book included printed listings for each and every source code file. This one does not. It contains printed copies of relevant source code files and provides the others on CD. Why? Because printing a source code file that's 50 percent meat and 50 percent fat adds bulk to a book without adding content. Some of the code produced by the MFC AppWizard in Visual C++ 6.0 won't even compile. (For details, see Chapter 4.) I'm not very proud of the parts of my book that the wizards created, because those portions are littered with arbitrary blank lines, comments that lack consistent style, and unnecessary functions. For someone who takes pride in writing concise, readable sample code, wizard output is a bitter pill to swallow.

Nevertheless, wizards represent the new world order in Windows programming, and they're something that you, I, and everyone else must get used to. It's a shame that the Visual C++ team won't give us real wizards to play with instead of the toys that they pass off as wizards today. Until they do, we must make do with what we have.

What's On the CD

The CD that accompanies this book contains source code and executables for all the sample programs presented in the book. All samples were written and compiled with Visual C++ 6.0 and MFC 6.0 and tested on various Win32 platforms. Unless otherwise noted, all are compatible with Windows 98, Windows NT 4.0, and Windows 2000. Most are also compatible with Windows 95 and Windows NT 3.51.

You can copy the contents of the CD to your hard disk by running the setup program found in the CD's root directory, or you can retrieve the files directly from the CD's \Code directory. The \Code directory contains one subdirectory for each chapter of the book—Chap01, Chap02, and so on. Inside these subdirectories you'll find the sample programs. Each set of source code files is accompanied by a release-build EXE as well as a Visual C++ workspace (DSW) file that you can open with Visual C++'s Open Workspace command.

From Me to You (and You to Me)

From the day in 1995 when I began writing the first edition of Programming Windows with MFC, my goal has been to provide C++ programmers with the same kind of timeless, irreplaceable resource that Programming Windows is to C programmers. Whether I've achieved that goal, I'll let you be the judge.

I want to know what you think about Programming Windows with MFC, and I particularly want to hear from you if you find mistakes. You can reach me by sending mail to jeffpro@msn.com or by visiting my Web site at www.prosise.com. At that site you'll find up-to-date information regarding the book, a list of errata, and information about other projects that I'm working on. Later this year, I plan to post a brand new chapter on MFC DLLs that you can read and comment on online.

With the huge volume of computer books vying for buyers' attention in bookstores today, I know that you could have chosen any number of MFC books besides this one. I thank you for purchasing Programming Windows with MFC, and I sincerely hope you conclude that your money was well spent. Enjoy!

Jeff Prosise
March 12, 1999

posted @ 2011-01-11 00:47 snddman 阅读(27) 评论(0) 编辑

A few short years ago, the person learning to program Microsoft Windows for the first time had a limited number of programming tools to choose from. C was the language spoken by the Windows Software Development Kit (SDK), and alternative Windows programming environments such as Microsoft Visual Basic hadn't arrived on the scene. Most Windows applications were written in C, and the fledgling Windows programmer faced the daunting task not only of learning the ins and outs of a new operating system but also of getting acquainted with the hundreds of different application programming interface (API) functions that Windows supports.

Today many Windows programs are still written in C. But the variety of Windows programming environments available means that commercial-quality Windows programs can be written in C, C++, Pascal, BASIC, and a number of other languages. Moreover, C++ has all but replaced C as the professional Windows programmer's language of choice because the sheer complexity of Windows, coupled with the wide-ranging scope of the Windows API, cries out for an object-oriented programming language. Many Windows programmers have concluded that C++ offers a compelling alternative to C that, combined with a class library that abstracts the API and encapsulates the basic behavior of windows and other objects in reusable classes, makes Windows programming simpler. And an overwhelming majority of C++ programmers have settled on the Microsoft Foundation Class library, better known by the acronym MFC, as their class library of choice. Other Windows class libraries are available, but only MFC was written by the company that writes the operating system. MFC is continually updated to incorporate the latest changes to Windows itself, and it provides a comprehensive set of classes representing everything from windows to ActiveX controls in order to make the job of writing Windows applications easier.

If you're coming to MFC from a traditional Windows programming environment such as C and the Windows SDK, you're already familiar with many of the concepts you need to know to understand Windows programming with MFC. But if you're coming from a character-oriented environment such as MS-DOS or UNIX, you'll find that Windows programming is fundamentally different from anything you've done before. This chapter begins with an overview of the Windows programming model and a peek under the hood at how Windows applications work. It continues with an introduction to MFC. After the preliminaries are out of the way, you'll develop your very first Windows application—one that uses MFC to create a resizeable window containing the message "Hello, MFC."

posted @ 2011-01-11 00:47 snddman 阅读(60) 评论(0) 编辑

The production of this book required the efforts of many people, but two in particular deserve to be singled out for their diligent, sustained, and unselfish efforts. Sally Stickney, the book's principal editor, navigated me through that minefield called the English language and contributed greatly to the book's readability. Marc Young, whose talents as a technical editor are nothing short of amazing, was relentless in tracking down bugs, testing sample code, and verifying facts. Sally, Marc: This book is immeasurably better because of you. Thanks.

posted @ 2011-01-11 00:46 snddman 阅读(13) 评论(0) 编辑

在一个文件中定义的变量和函数如何能被其他文件使用,不会出现多重定义和未定义的编译错误,这要使用好头文件和关键字extern。

大家都知道extern是声明外部变量的时候使用的,而函数声明默认就是extern的。

如果在一个文件中定义一个变量a,

int a;

那么只有本文件可以使用a变量,其他的文件看不到。其他文件可以使用extern声明a

extern int a;

这个语句的功能是,a变量已经被其他文件定义了,这里声明一下后,可以在本文件中使用。如果在每个文件中都声明一下每个外部变量,难免有点太繁了,我们可以在一个文件中定义变量,然后在这个文件对应的头文件中对这个变量加以extern声明,然后其它文件要使用变量的时候,只要把这个头文件包含进去就行了。

函数由于本身就是extern的,所以在头文件中声明的时候不需要加extern,只要像常规的声明一下即可。补充说明一下,头文件中只可以声明变量和函数,不要出现任何形式的定义,否则很容易出现多重定义。所有的定义都交给源程序(*.c或者*.cpp)去定义。

posted @ 2010-06-22 17:15 snddman 阅读(533) 评论(0) 编辑