50% found this document useful (4 votes)
12K views676 pages

Programming Microsoft Visual C++

This chapter summarizes the Windows programming model (Win32 in particular) it shows you how the Microsoft Visual C++ components work together to help you write applications for Windows. The Windows programming model is different from old-style batch-oriented or transaction-oriented programming.

Uploaded by

aeroarunn
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
50% found this document useful (4 votes)
12K views676 pages

Programming Microsoft Visual C++

This chapter summarizes the Windows programming model (Win32 in particular) it shows you how the Microsoft Visual C++ components work together to help you write applications for Windows. The Windows programming model is different from old-style batch-oriented or transaction-oriented programming.

Uploaded by

aeroarunn
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 676

-1-

Copyright© 1998 by David J. Kruglinski


Chapter One
1. Microsoft Windows and Visual C++
Enough has already been written about the acceptance of Microsoft Windows and the benefits of the graphical user
interface (GUI). This chapter summarizes the Windows programming model (Win32 in particular) and shows you how
the Microsoft Visual C++ components work together to help you write applications for Windows. Along the way, you
might learn some new things about Windows.
2. The Windows Programming Model
No matter which development tools you use, programming for Windows is different from old-style batch-oriented or
transaction-oriented programming. To get started, you need to know some Windows fundamentals. As a frame of
reference, we'll use the well-known MS-DOS programming model. Even if you don't currently program for plain MS-
DOS, you're probably familiar with it.
2.1 Message Processing
When you write an MS-DOS-based application in C, the only absolute requirement is a function named main. The
operating system calls main when the user runs the program, and from that point on, you can use any programming
structure you want. If your program needs to get user keystrokes or otherwise use operating system services, it calls
an appropriate function, such as getchar, or perhaps uses a character-based windowing library.
When the Windows operating system launches a program, it calls the program's WinMain function. Somewhere your
application must have WinMain, which performs some specific tasks. Its most important task is creating the
application's main window, which must have its own code to process messages that Windows sends it. An essential
difference between a program written for MS-DOS and a program written for Windows is that an MS-DOS-based
program calls the operating system to get user input, but a Windows-based program processes user input via
messages from the operating system.

Many development environments for Windows, including Microsoft Visual C++ version 6.0 with the
Microsoft Foundation Class (MFC) Library version 6.0, simplify programming by hiding the WinMain
function and structuring the message-handling process. When you use the MFC library, you need
not write a WinMain function but it is essential that you understand the link between the operating
system and your programs.
Most messages in Windows are strictly defined and apply to all programs. For example, a WM_CREATE message is
sent when a window is being created, a WM_LBUTTONDOWN message is sent when the user presses the left
mouse button, a WM_CHAR message is sent when the user types a character, and a WM_CLOSE message is sent
when the user closes a window. All messages have two 32-bit parameters that convey information such as cursor
coordinates, key code, and so forth. Windows sends WM_COMMAND messages to the appropriate window in
response to user menu choices, dialog button clicks, and so on. Command message parameters vary depending on
the window's menu layout. You can define your own messages, which your program can send to any window on the
desktop. These user-defined messages actually make C++ look a little like Smalltalk.
-2-

Don't worry yet about how these messages are connected to your code. That's the job of the application framework.
Be aware, though, that the Windows message processing requirement imposes a lot of structure on your program.
Don't try to force your Windows programs to look like your old MS-DOS programs. Study the examples in this book,
and then be prepared to start fresh.
2.2 The Windows Graphics Device Interface
Many MS-DOS programs wrote directly to the video memory and the printer port. The disadvantage of this technique
was the need to supply driver software for every video board and every printer model. Windows introduced a layer of
abstraction called the Graphics Device Interface (GDI). Windows provides the video and printer drivers, so your
program doesn't need to know the type of video board and printer attached to the system. Instead of addressing the
hardware, your program calls GDI functions that reference a data structure called a device context. Windows maps
the device context structure to a physical device and issues the appropriate input/output instructions. The GDI is
almost as fast as direct video access, and it allows different applications written for Windows to share the display.
2.3 Resource-Based Programming
To do data-driven programming in MS-DOS, you must either code the data as initialization constants or provide
separate data files for your program to read. When you program for Windows, you store data in a resource file using
a number of established formats. The linker combines this binary resource file with the C++ compiler's output to
generate an executable program. Resource files can include bitmaps, icons, menu definitions, dialog box layouts,
and strings. They can even include custom resource formats that you define.
You use a text editor to edit a program, but you generally use wysiwyg (what you see is what you get) tools to edit
resources. If you're laying out a dialog box, for example, you select elements (buttons, list boxes, and so forth) from
an array of icons called a control palette, and you position and size the elements with the mouse. Microsoft Visual C+
+ 6.0 has graphics resource editors for all standard resource formats.
2.4 Memory Management
With each new version of Windows, memory management gets easier. If you've heard horror stories about locking
memory handles, thunks, and burgermasters, don't worry. That's all in the past. Today you simply allocate the
memory you need, and Windows takes care of the details. Chapter 10 describes current memory management
techniques for Win32, including virtual memory and memory-mapped files.
2.5 Dynamic Link Libraries
In the MS-DOS environment, all of a program's object modules are statically linked during the build process.
Windows allows dynamic linking, which means that specially constructed libraries can be loaded and linked at
runtime. Multiple applications can share dynamic link libraries (DLLs), which saves memory and disk space. Dynamic
linking increases program modularity because you can compile and test DLLs separately.
Designers originally created DLLs for use with the C language, and C++ has added some complications. The MFC
developers succeeded in combining all the application framework classes into a few ready-built DLLs. This means
that you can statically or dynamically link the application framework classes into your application. In addition, you can
create your own extension DLLs that build on the MFC DLLs. Chapter 22 includes information about creating MFC
extension DLLs and regular DLLs.
2.6 The Win32 Application Programming Interface
Early Windows programmers wrote applications in C for the Win16 application programming interface (API). Today, if
you want to write 32-bit applications, you must use the new Win32 API, either directly or indirectly. Most Win16
functions have Win32 equivalents, but many of the parameters are different—16-bit parameters are often replaced
with 32-bit parameters, for example. The Win32 API offers many new functions, including functions for disk I/O,
which was formerly handled by MS-DOS calls. With the 16-bit versions of Visual C++, MFC programmers were
largely insulated from these API differences because they wrote to the MFC standard, which was designed to work
with either Win16 or Win32 underneath.
3. The Visual C++ Components
Microsoft Visual C++ is two complete Windows application development systems in one product. If you so choose,
you can develop C-language Windows programs using only the Win32 API. C-language Win32 programming is
described in Charles Petzold's book Programming Windows 95 (Microsoft Press, 1996). You can use many Visual
C++ tools, including the resource editors, to make low-level Win32 programming easier.
Visual C++ also includes the ActiveX Template Library (ATL), which you can use to develop ActiveX controls for the
Internet. ATL programming is neither Win32 C-language programming nor MFC programming, and it's complex
enough to deserve its own book.
This book is not about C-language Win32 programming or ATL programming (although Chapter 29 and Chapter 30
provide an introduction to ATL). It's about C++ programming within the MFC library application framework that's part
of Visual C++. You'll be using the C++ classes documented in the Microsoft Visual C++ MFC Library Reference
(Microsoft Press, 1997), and you'll also be using application framework-specific Visual C++ tools such as AppWizard
and ClassWizard.
-3-

Use of the MFC library programming interface doesn't cut you off from the Win32 functions. In fact,
you'll almost always need some direct Win32 calls in your MFC library programs.
A quick run-through of the Visual C++ components will help you get your bearings before you zero in on the
application framework. Figure 1-1 shows an overview of the Visual C++ application build process.

Figure 1-1. The Visual C++ application build process.


3.1 Microsoft Visual C++ 6.0 and the Build Process
Visual Studio 6.0 is a suite of developer tools that includes Visual C++ 6.0. The Visual C++ IDE is shared by several
tools including Microsoft Visual J++. The IDE has come a long way from the original Visual Workbench, which was
based on QuickC for Windows. Docking windows, configurable toolbars, plus a customizable editor that runs macros,
are now part of Visual Studio. The online help system (now integrated with the MSDN Library viewer) works like a
Web browser. Figure 1-2 shows Visual C++ 6.0 in action.

Figure 1-2. Visual C++ 6.0 windows.


If you've used earlier versions of Visual C++ or another vendor's IDE, you already understand how Visual C++ 6.0
operates. But if you're new to IDEs, you'll need to know what a project is. A project is a collection of interrelated
-4-

source files that are compiled and linked to make up an executable Windows-based program or a DLL. Source files
for each project are generally stored in a separate subdirectory. A project depends on many files outside the project
subdirectory too, such as include files and library files.
Experienced programmers are familiar with makefiles. A makefile stores compiler and linker options and expresses
all the interrelationships among source files. (A source code file needs specific include files, an executable file
requires certain object modules and libraries, and so forth.) A make program reads the makefile and then invokes the
compiler, assembler, resource compiler, and linker to produce the final output, which is generally an executable file.
The make program uses built-in inference rules that tell it, for example, to invoke the compiler to generate an OBJ file
from a specified CPP file.
In a Visual C++ 6.0 project, there is no makefile (with an MAK extension) unless you tell the system to export one. A
text-format project file (with a DSP extension) serves the same purpose. A separate text-format workspace file (with
a DSW extension) has an entry for each project in the workspace. It's possible to have multiple projects in a
workspace, but all the examples in this book have just one project per workspace. To work on an existing project,
you tell Visual C++ to open the DSW file and then you can edit and build the project.
Visual C++ creates some intermediate files too. The following table lists the files that Visual C++ generates in the
workspace.
File Extension Description

APS Supports ResourceView

BSC Browser information file


CLW Supports ClassWizard

DEP Dependency file

DSP Project file*

DSW Workspace file*

MAK External makefile

NCB Supports ClassView

OPT Holds workspace configuration

PLG Builds log file


* Do not delete or edit in a text editor.
3.2 The Resource Editors—Workspace ResourceView
When you click on the ResourceView tab in the Visual C++ Workspace window, you can select a resource for
editing. The main window hosts a resource editor appropriate for the resource type. The window can also host a
wysiwyg editor for menus and a powerful graphical editor for dialog boxes, and it includes tools for editing icons,
bitmaps, and strings. The dialog editor allows you to insert ActiveX controls in addition to standard Windows controls
and the new Windows common controls (which have been further extended in Visual C++ 6.0). Chapter 3 shows
pictures of the ResourceView page and one of the resource editors (the dialog editor).
Each project usually has one text-format resource script (RC) file that describes the project's menu, dialog, string,
and accelerator resources. The RC file also has #include statements to bring in resources from other subdirectories.
These resources include project-specific items, such as bitmap (BMP) and icon (ICO) files, and resources common
to all Visual C++ programs, such as error message strings. Editing the RC file outside the resource editors is not
recommended. The resource editors can also process EXE and DLL files, so you can use the clipboard to "steal"
resources, such as bitmaps and icons, from other Windows applications.
3.3 The C/C++ Compiler
The Visual C++ compiler can process both C source code and C++ source code. It determines the language by
looking at the source code's filename extension. A C extension indicates C source code, and CPP or CXX indicates
C++ source code. The compiler is compliant with all ANSI standards, including the latest recommendations of a
working group on C++ libraries, and has additional Microsoft extensions. Templates, exceptions, and runtime type
identification (RTTI) are fully supported in Visual C++ version 6.0. The C++ Standard Template Library (STL) is also
included, although it is not integrated into the MFC library.
3.4 The Source Code Editor
Visual C++ 6.0 includes a sophisticated source code editor that supports many features such as dynamic syntax
coloring, auto-tabbing, keyboard bindings for a variety of popular editors (such as VI and EMACS), and pretty
-5-

printing. In Visual C++ 6.0, an exciting new feature named AutoComplete has been added. If you have used any of
the Microsoft Office products or Microsoft Visual Basic, you might already be familiar with this technology. Using the
Visual C++ 6.0 AutoComplete feature, all you have to do is type the beginning of a programming statement and the
editor will provide you with a list of possible completions to choose from. This feature is extremely handy when you
are working with C++ objects and have forgotten an exact member function or data member name—they are all
there in the list for you. You no longer have to memorize thousands of Win32 APIs or rely heavily on the online help
system, thanks to this new feature.
3.5 The Resource Compiler
The Visual C++ resource compiler reads an ASCII resource script (RC) file from the resource editors and writes a
binary RES file for the linker.
3.6 The Linker
The linker reads the OBJ and RES files produced by the C/C++ compiler and the resource compiler, and it accesses
LIB files for MFC code, runtime library code, and Windows code. It then writes the project's EXE file. An incremental
link option minimizes the execution time when only minor changes have been made to the source files. The MFC
header files contain #pragma statements (special compiler directives) that specify the required library files, so you
don't have to tell the linker explicitly which libraries to read.
3.7 The Debugger
If your program works the first time, you don't need the debugger. The rest of us might need one from time to time.
The Visual C++ debugger has been steadily improving, but it doesn't actually fix the bugs yet. The debugger works
closely with Visual C++ to ensure that breakpoints are saved on disk. Toolbar buttons insert and remove breakpoints
and control single-step execution. Figure 1-3 illustrates the Visual C++ debugger in action. Note that the Variables
and Watch windows can expand an object pointer to show all data members of the derived class and base classes. If
you position the cursor on a simple variable, the debugger shows you its value in a little window. To debug a
program, you must build the program with the compiler and linker options set to generate debugging information.

Figure 1-3. The Visual C++ debugger window.


Visual C++ 6.0 adds a new twist to debugging with the Edit And Continue feature. Edit And Continue lets you debug
an application, change the application, and then continue debugging with the new code. This feature dramatically
reduces the amount of time you spend debugging because you no longer have to manually leave the debugger,
recompile, and then debug again. To use this feature, simply edit any code while in the debugger and then hit the
continue button. Visual C++ 6.0 automatically compiles the changes and restarts the debugger for you.
3.8 AppWizard
AppWizard is a code generator that creates a working skeleton of a Windows application with features, class names,
and source code filenames that you specify through dialog boxes. You'll use AppWizard extensively as you work
through the examples in this book. Don't confuse AppWizard with older code generators that generate all the code
for an application. AppWizard code is minimalist code; the functionality is inside the application framework base
classes. AppWizard gets you started quickly with a new application.
-6-

Advanced developers can build custom AppWizards. Microsoft Corporation has exposed its macro-based system for
generating projects. If you discover that your team needs to develop multiple projects with a telecommunications
interface, you can build a special wizard that automates the process.
3.9 ClassWizard
ClassWizard is a program (implemented as a DLL) that's accessible from Visual C++'s View menu. ClassWizard
takes the drudgery out of maintaining Visual C++ class code. Need a new class, a new virtual function, or a new
message-handler function? ClassWizard writes the prototypes, the function bodies, and (if necessary) the code to
link the Windows message to the function. ClassWizard can update class code that you write, so you avoid the
maintenance problems common to ordinary code generators. Some ClassWizard features are available from Visual
C++'s WizardBar toolbar, shown in Figure 1-2.
3.10 The Source Browser
If you write an application from scratch, you probably have a good mental picture of your source code files, classes,
and member functions. If you take over someone else's application, you'll need some assistance. The Visual C++
Source Browser (the browser, for short) lets you examine (and edit) an application from the class or function
viewpoint instead of from the file viewpoint. It's a little like the "inspector" tools available with object-oriented libraries
such as Smalltalk. The browser has the following viewing modes:
Definitions and References—You select any function, variable, type, macro, or class and then see where it's
defined and used in your project.
Call Graph/Callers Graph—For a selected function, you'll see a graphical representation of the functions it calls or
the functions that call it.
Derived Classes and Members/Base Classes and Members—These are graphical class hierarchy diagrams. For a
selected class, you see the derived classes or the base classes plus members. You can control the hierarchy
expansion with the mouse.
File Outline—For a selected file, the classes, functions, and data members appear together with the places in
which they're defined and used in your project.
A typical browser window is shown in Chapter 3.

If you rearrange the lines in any source code file, Visual C++ regenerates the browser database
when you rebuild the project. This increases the build time.
In addition to the browser, Visual C++ has a ClassView option that does not depend on the browser database. You
get a tree view of all the classes in your project, showing member functions and data members. Double-click on an
element, and you see the source code immediately. The ClassView does not show hierarchy information, whereas
the browser does.
3.11 Online Help
In Visual C++ 6.0, the help system has been moved to a separate application named the MSDN Library Viewer. This
help system is based on HTML. Each topic is covered in an individual HTML document; then all are combined into
indexed files. The MSDN Library Viewer uses code from Microsoft Internet Explorer 4.0, so it works like the Web
browser you already know. MSDN Library can access the help files from the Visual C++ CD-ROM (the default
installation option) or from your hard disk, and it can access HTML files on the Internet.
Visual C++ 6.0 allows you to access help in four ways:
By book—When you choose Contents from Visual C++'s Help menu, the MSDN Library application switches to a
contents view. Here Visual Studio, Visual C++, Win32 SDK documentation, and more is organized hierarchically
by books and chapters.
By topic—When you choose Search from Visual C++'s Help menu, it automatically opens the MSDN Library
Viewer. You can then select the Index tab, type a keyword, and see the topics and articles included for that
keyword.
By word—When you choose Search from Visual C++'s Help menu, it invokes the MSDN Library with the Search
tab active. With this tab active, you can type a combination of words to view articles that contain those words.
F1 help—This is the programmer's best friend. Just move the cursor inside a function, macro, or class name, and
then press the F1 key and the help system goes to work. If the name is found in several places—in the MFC and
Win32 help files, for example—you choose the help topic you want from a list window.
Whichever way you access online help, you can copy any help text to the clipboard for inclusion in your program.
3.12 Windows Diagnostic Tools
Visual C++ 6.0 contains a number of useful diagnostic tools. SPY++ gives you a tree view of your system's
processes, threads, and windows. It also lets you view messages and examine the windows of running applications.
You'll find PVIEW (PVIEW95 for Windows 95) useful for killing errant processes that aren't visible from the Windows
95 task list. (The Windows NT Task Manager, which you can run by right-clicking the toolbar, is an alternative to
PVIEW.) Visual C++ also includes a whole suite of ActiveX utilities, an ActiveX control test program (now with full
-7-

source code in Visual C++ 6.0), the help workshop (with compiler), a library manager, binary file viewers and editors,
a source code profiler, and other utilities.
3.13 Source Code Control
During development of Visual C++ 5.0, Microsoft bought the rights to an established source code control product
named SourceSafe. This product has since been included in the Enterprise Edition of Visual C++ and Visual Studio
Enterprise, and it is integrated into Visual C++ so that you can coordinate large software projects. The master copy
of the project's source code is stored in a central place on the network, and programmers can check out modules for
updates. These checked-out modules are usually stored on the programmer's local hard disk. After a programmer
checks in modified files, other team members can synchronize their local hard disk copies to the master copy. Other
source code control systems can also be integrated into Visual C++.
3.14 The Gallery
The Visual C++ Components and Controls Gallery lets you share software components among different projects.
The Gallery manages three types of modules:
ActiveX controls—When you install an ActiveX control (OCX—formerly OLE control), an entry is made in the
Windows Registry. All registered ActiveX controls appear in the Gallery's window, so you can select them in any
project.
C++ source modules—When you write a new class, you can add the code to the Gallery. The code can then be
selected and copied into other projects. You can also add resources to the Gallery.
Visual C++ components—The Gallery can contain tools that let you add features to your project. Such a tool could
insert new classes, functions, data members, and resources into an existing project. Some component modules
are supplied by Microsoft (Idle time processing, Palette support, and Splash screen, for example) as part of Visual
C++. Others will be supplied by third-party soft-ware firms.

If you decide to use one of the prepackaged Visual C++ components, try it out first in a dummy
project to see if it's what you really want. Otherwise, it might be difficult to remove the generated
code from your regular project.
All user-generated Gallery items can be imported from and exported to OGX files. These files are the distribution and
sharing medium for Visual C++ components.
3.15 The Microsoft Foundation Class Library Version 6.0
The Microsoft Foundation Class Library version 6.0 (the MFC library, for short) is really the subject of this book. It
defines the application framework that you'll be learning intimately. Chapter 2 gets you started with actual code and
introduces some important concepts.
3.16 The Microsoft Active Template Library
ATL is a tool, separate from MFC, for building ActiveX controls. You can build ActiveX controls with either MFC or
ATL, but ATL controls are much smaller and quicker to load on the Internet. Chapter 29 and Chapter 30 provide a
brief overview of ATL and creating ActiveX controls with ATL.
Chapter Two
4. The Microsoft Foundation Class Library Application Framework
This chapter introduces the Microsoft Foundation Class Library version 6.0 (the MFC library) application framework
by explaining its benefits. You'll see a stripped-down but fully operational MFC library program for Microsoft Windows
that should help you understand what application framework programming is all about. Theory is kept to a minimum
here, but the sections on message mapping and on documents and views contain important information that will help
you with the examples in later chapters.
5. Why Use the Application Framework?
If you're going to develop applications for Windows, you've got to choose a development environment. Assuming that
you've already rejected non-C options such as Microsoft Visual Basic and Borland Delphi, here are some of your
remaining options:
Program in C with the Win32 API
Write your own C++ Windows class library that uses Win32
Use the MFC application framework
Use another Windows-based application framework such as Borland's Object Windows Library (OWL)
If you're starting from scratch, any option involves a big learning curve. If you're already a Win16 or Win32
programmer, you'll still have a learning curve with the MFC library. Since its release, MFC has become the dominant
Windows class library. But even if you're familiar with it, it's still a good idea to step through the features of this
programming choice.
The MFC library is the C++ Microsoft Windows API. If you accept the premise that the C++ language is now the
standard for serious application development, you'd have to say that it's natural for Windows to have a C++
-8-

programming interface. What better interface is there than the one produced by Microsoft, creator of Windows? That
interface is the MFC library.
Application framework applications use a standard structure. Any programmer starting on a large project develops
some kind of structure for the code. The problem is that each programmer's structure is different, and it's difficult for
a new team member to learn the structure and conform to it. The MFC library application framework includes its own
application structure—one that's been proven in many software environments and in many projects. If you write a
program for Windows that uses the MFC library, you can safely retire to a Caribbean island, knowing that your
minions can easily maintain and enhance your code back home.
Don't think that the MFC library's structure makes your programs inflexible. With the MFC library, your program can
call Win32 functions at any time, so you can take maximum advantage of Windows.
Application framework applications are small and fast. Back in the 16-bit days, you could build a self-contained
Windows EXE file that was less than 20 kilobytes (KB) in size. Today, Windows programs are larger. One reason is
that 32-bit code is fatter. Even with the large memory model, a Win16 program used 16-bit addresses for stack
variables and many globals. Win32 programs use 32-bit addresses for everything and often use 32-bit integers
because they're more efficient than 16-bit integers. In addition, the new C++ exception-handling code consumes a lot
of memory.
That old 20-KB program didn't have a docking toolbar, splitter windows, print preview capabilities, or control
container support—features that users expect in modern programs. MFC programs are bigger because they do more
and look better. Fortunately, it's now easy to build applications that dynamically link to the MFC code (and to C
runtime code), so the size goes back down again—from 192 KB to about 20 KB! Of course, you'll need some big
support DLLs in the background, but those are a fact of life these days.
As far as speed is concerned, you're working with machine code produced by an optimizing compiler. Execution is
fast, but you might notice a startup delay while the support DLLs are loaded.
The Visual C++ tools reduce coding drudgery. The Visual C++ resource editors, AppWizard, and ClassWizard
significantly reduce the time needed to write code that is specific to your application. For example, the resource
editor creates a header file that contains assigned values for #define constants. App-Wizard generates skeleton code
for your entire application, and ClassWizard generates prototypes and function bodies for message handlers.
The MFC library application framework is feature rich. The MFC library version 1.0 classes, introduced with Microsoft
C/C++ version 7.0, included the following features:
A C++ interface to the Windows API
General-purpose (non-Windows-specific) classes, including:
Collection classes for lists, arrays, and maps
A useful and efficient string class
Time, time span, and date classes
File access classes for operating system independence
Support for systematic object storage and retrieval to and from disk
A "common root object" class hierarchy
Streamlined Multiple Document Interface (MDI) application support
Some support for OLE version 1.0
The MFC library version 2.0 classes (in Visual C++ version 1.0) picked up where the version 1.0 classes left off by
supporting many user interface features that are found in current Windows-based applications, plus they introduced
the application framework architecture. Here's a summary of the important new features:
Full support for File Open, Save, and Save As menu items and the most recently used file list
Print preview and printer support
Support for scrolling windows and splitter windows
Support for toolbars and status bars
Access to Visual Basic controls
Support for context-sensitive help
Support for automatic processing of data entered in a dialog box
An improved interface to OLE version 1.0
DLL support
The MFC library version 2.5 classes (in Visual C++ version 1.5) contributed the following:
Open Database Connectivity (ODBC) support that allows your application to access and update data stored in
many popular databases such as Microsoft Access, FoxPro, and Microsoft SQL Server
An interface to OLE version 2.01, with support for in-place editing, linking, drag and drop, and OLE Automation
Visual C++ version 2.0 was the first 32-bit version of the product. It included support for Microsoft Windows NT
version 3.5. It also contained MFC version 3.0, which had the following new features:
-9-

Tab dialog (property sheet) support (which was also added to Visual C++ version 1.51, included on the same CD-
ROM)
Docking control bars that were implemented within MFC
Support for thin-frame windows
A separate Control Development Kit (CDK) for building 16-bit and 32-bit OLE controls, although no OLE control
container support was provided
A subscription release, Visual C++ 2.1 with MFC 3.1, added the following:
Support for the new Microsoft Windows 95 (beta) common controls
A new ODBC Level 2 driver integrated with the Access Jet database engine
Winsock classes for TCP/IP data communication
Microsoft decided to skip Visual C++ version 3.0 and proceeded directly to 4.0 in order to synchronize the product
version with the MFC version. MFC 4.0 contains these additional features:
New OLE-based Data Access Objects (DAO) classes for use with the Jet engine
Use of the Windows 95 docking control bars instead of the MFC control bars
Full support for the common controls in the released version of Windows 95, with new tree view and rich-edit view
classes
New classes for thread synchronization
OLE control container support
Visual C++ 4.2 was an important subscription release that included MFC version 4.2. The following new features
were included:
WinInet classes
ActiveX Documents server classes
ActiveX synchronous and asynchronous moniker classes
Enhanced MFC ActiveX Control classes, with features such as windowless activation, optimized drawing code,
and so forth
Improved MFC ODBC support, including recordset bulk fetches and data transfer without binding
Visual C++ 5.0 included MFC version 4.21, which fixed some 4.2 bugs. Visual C++ 5.0 introduced some worthwhile
features of its own as well:
A redesigned IDE, Developer Studio 97, which included an HTML-based online help system and integration with
other languages, including Java
The Active Template Library (ATL) for efficient ActiveX control construction for the Internet
C++ language support for COM (Component Object Model) client programs with the new #import statement for
type libraries, as described in Chapter 25
The latest edition of Visual C++, 6.0, includes MFC 6.0. (Notice that the versions are now synchronized again.) Many
of the features in MFC 6.0 enable the developer to support the new Microsoft Active Platform, including the following:
MFC classes that encapsulate the new Windows common controls introduced as part of Internet Explorer 4.0
Support for Dynamic HTML, which allows the MFC programmer to create applications that can dynamically
manipulate and generate HTML pages
Active Document Containment, which allows MFC-based applications to contain Active Documents
OLE DB Consumers and Providers Template support and Active Data Objects (ADO) data binding, which help
database developers who use MFC or ATL
5.1 The Learning Curve
All the listed benefits sound great, don't they? You're probably thinking, "You don't get something for nothing." Yes,
that's true. To use the application framework effectively, you have to learn it thoroughly, and that takes time. If you
have to learn C++, Windows, and the MFC library (without OLE) all at the same time, it will take at least six months
before you're really productive. Interestingly, that's close to the learning time for the Win32 API alone.
How can that be if the MFC library offers so much more? For one thing, you can avoid many programming details
that C-language Win32 programmers are forced to learn. From our own experience, we can say that an object-
oriented application framework makes programming for Windows easier to learn—that is, once you understand
object-oriented programming.
The MFC library won't bring real Windows programming down to the masses. Programmers of applications for
Windows have usually commanded higher salaries than other programmers, and that situation will continue. The
MFC library's learning curve, together with the application framework's power, should ensure that MFC library
programmers will continue to be in strong demand.
6. What's an Application Framework?
One definition of application framework is "an integrated collection of object-oriented software components that offers
all that's needed for a generic application." That isn't a very useful definition, is it? If you really want to know what an
- 10 -

application framework is, you'll have to read the rest of this book. The application framework example that you'll
familiarize yourself with later in this chapter is a good starting point.
6.1 An Application Framework vs. a Class Library
One reason that C++ is a popular language is that it can be "extended" with class libraries. Some class libraries are
delivered with C++ compilers, others are sold by third-party software firms, and still others are developed in-house. A
class library is a set of related C++ classes that can be used in an application. A mathematics class library, for
example, might perform common mathematics operations, and a communications class library might support the
transfer of data over a serial link. Sometimes you construct objects of the supplied classes; sometimes you derive
your own classes—it all depends on the design of the particular class library.
An application framework is a superset of a class library. An ordinary library is an isolated set of classes designed to
be incorporated into any program, but an application framework defines the structure of the program itself. Microsoft
didn't invent the application framework concept. It appeared first in the academic world, and the first commercial
version was MacApp for the Apple Macintosh. Since MFC 2.0 was introduced, other companies, including Borland,
have released similar products.
6.2 An Application Framework Example
Enough generalizations. It's time to look at some code—not pseudocode but real code that actually compiles and
runs with the MFC library. Guess what? It's the good old "Hello, world!" application, with a few additions. (If you've
used version 1.0 of the MFC library, this code will be familiar except for the frame window base class.) It's about the
minimum amount of code for a working MFC library application for Windows. (Contrast it with an equivalent pure
Win32 application such as you would see in a Petzold book!) You don't have to understand every line now. Don't
bother to type it in and test it, because EX23B on the CD-ROM is quite similar. Wait for the next chapter, where you'll
start using the "real" application framework.

By convention, MFC library class names begin with the letter C.


Following is the source code for the header and implementation files for our MYAPP application. The classes
CMyApp and CMyFrame are each derived from MFC library base classes. First, here is the MyApp.h header file for
the MYAPP application:
// application class
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};

// frame window class


class CMyFrame : public CFrameWnd
{
public:
CMyFrame();
protected:
// "afx_msg" indicates that the next two functions are part
// of the MFC library message dispatch system
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnPaint();
DECLARE_MESSAGE_MAP()
};
And here is the MyApp.cpp implementation file for the MYAPP application:
#include <afxwin.h> // MFC library header file declares base classes
#include "myapp.h"

CMyApp theApp; // the one and only CMyApp object

BOOL CMyApp::InitInstance()
{
m_pMainWnd = new CMyFrame();
m_pMainWnd->ShowWindow(m_nCmdShow);
- 11 -

m_pMainWnd->UpdateWindow();
return TRUE;
}

BEGIN_MESSAGE_MAP(CMyFrame, CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()

CMyFrame::CMyFrame()
{
Create(NULL, "MYAPP Application");
}

void CMyFrame::OnLButtonDown(UINT nFlags, CPoint point)


{
TRACE("Entering CMyFrame::OnLButtonDown - %lx, %d, %d\n",
(long) nFlags, point.x, point.y);
}

void CMyFrame::OnPaint()
{
CPaintDC dc(this);
dc.TextOut(0, 0, "Hello, world!");
}
Here are some of the program elements:
The WinMain function—Remember that Windows requires your application to have a WinMain function. You don't
see WinMain here because it's hidden inside the application framework.
The CMyApp class—An object of class CMyApp represents an application. The program defines a single global
CMyApp object, theApp. The CWinApp base class determines most of theApp's behavior.
Application startup—When the user starts the application, Windows calls the application framework's built-in
WinMain function, and WinMain looks for your globally constructed application object of a class derived from
CWinApp. Don't forget that in a C++ program global objects are constructed before the main program is executed.
The CMyApp::InitInstance member function—When the WinMain function finds the application object, it calls the
virtual InitInstance member function, which makes the calls needed to construct and display the application's main
frame window. You must override InitInstance in your derived application class because the CWinApp base class
doesn't know what kind of main frame window you want.
The CWinApp::Run member function—The Run function is hidden in the base class, but it dispatches the
application's messages to its windows, thus keeping the application running. WinMain calls Run after it calls
InitInstance.
The CMyFrame class—An object of class CMyFrame represents the application's main frame window. When the
constructor calls the Create member function of the base class CFrameWnd, Windows creates the actual window
structure and the application framework links it to the C++ object. The ShowWindow and UpdateWindow functions,
also member functions of the base class, must be called in order to display the window.
The CMyFrame::OnLButtonDown function—This function is a sneak preview of the MFC library's message-handling
capability. We've elected to "map" the left mouse button down event to a CMyFrame member function. You'll learn
the details of the MFC library's message mapping in Chapter 4. For the time being, accept that this function gets
called when the user presses the left mouse button. The function invokes the MFC library TRACE macro to display a
message in the debugging window.
The CMyFrame::OnPaint function—The application framework calls this important mapped member function of class
CMyFrame every time it's necessary to repaint the window: at the start of the program, when the user resizes the
window, and when all or part of the window is newly exposed. The CPaintDC statement relates to the Graphics
Device Interface (GDI) and is explained in later chapters. The TextOut function displays "Hello, world!"
Application shutdown—The user shuts down the application by closing the main frame window. This action initiates a
sequence of events, which ends with the destruction of the CMyFrame object, the exit from Run, the exit from
WinMain, and the destruction of the CMyApp object.
- 12 -

Look at the code example again. This time try to get the big picture. Most of the application's functionality is in the
MFC library base classes CWinApp and CFrameWnd. In writing MYAPP, we've followed a few simple structure rules
and we've written key functions in our derived classes. C++ lets us "borrow" a lot of code without copying it. Think of
it as a partnership between us and the application framework. The application framework provided the structure, and
we provided the code that made the application unique.
Now you're beginning to see why the application framework is more than just a class library. Not only does the
application framework define the application structure but it also encompasses more than C++ base classes. You've
already seen the hidden WinMain function at work. Other elements support message processing, diagnostics, DLLs,
and so forth.
7. MFC Library Message Mapping
Refer to the OnLButtonDown member function in the previous example application. You might think that
OnLButtonDown would be an ideal candidate for a virtual function. A window base class would define virtual
functions for mouse event messages and other standard messages, and derived window classes could override the
functions as necessary. Some Windows class libraries do work this way.
The MFC library application framework doesn't use virtual functions for Windows messages. Instead, it uses macros
to "map" specified messages to derived class member functions. Why the rejection of virtual functions? Suppose
MFC used virtual functions for messages. The CWnd class would declare virtual functions for more than 100
messages. C++ requires a virtual function dispatch table, called a vtable, for each derived class used in a program.
Each vtable needs one 4-byte entry for each virtual function, regardless of whether the functions are actually
overridden in the derived class. Thus, for each distinct type of window or control, the application would need a table
consisting of over 400 bytes to support virtual message handlers.
What about message handlers for menu command messages and messages from button clicks? You couldn't define
these as virtual functions in a window base class because each application might have a different set of menu
commands and buttons. The MFC library message map system avoids large vtables, and it accommodates
application-specific command messages in parallel with ordinary Windows messages. It also allows selected
nonwindow classes, such as document classes and the application class, to handle command messages. MFC uses
macros to connect (or map) Windows messages to C++ member functions. No extensions to the C++ language are
necessary.
An MFC message handler requires a function prototype, a function body, and an entry (macro invocation) in the
message map. ClassWizard helps you add message handlers to your classes. You select a Windows message ID
from a list box, and the wizard generates the code with the correct function parameters and return values.
8. Documents and Views
The previous example used an application object and a frame window object. Most of your MFC library applications
will be more complex. Typically, they'll contain application and frame classes plus two other classes that represent
the "document" and the "view." This document-view architecture is the core of the application framework and is
loosely based on the Model/View/Controller classes from the Smalltalk world.
In simple terms, the document-view architecture separates data from the user's view of the data. One obvious benefit
is multiple views of the same data. Consider a document that consists of a month's worth of stock quotes stored on
disk. Suppose a table view and a chart view of the data are both available. The user updates values through the
table view window, and the chart view window changes because both windows display the same information (but in
different views).
In an MFC library application, documents and views are represented by instances of C++ classes. Figure 2-1 shows
three objects of class CStockDoc corresponding to three companies: AT&T, IBM, and GM. All three documents have
a table view attached, and one document also has a chart view. As you can see, there are four view objects—three
objects of class CStockTableView and one of class CStockChartView.
- 13 -

Figure 2-1. The document-view relationship.


The document base class code interacts with the File Open and File Save menu items; the derived document class
does the actual reading and writing of the document object's data. (The application framework does most of the work
of displaying the File Open and File Save dialog boxes and opening, closing, reading, and writing files.) The view
base class represents a window contained inside a frame window; the derived view class interacts with its associated
document class and does the application's display and printer I/O. The derived view class and its base classes
handle Windows messages. The MFC library orchestrates all interactions among documents, views, frame windows,
and the application object, mostly through virtual functions.
Don't think that a document object must be associated with a disk file that is read entirely into memory. If a
"document" were really a database, for example, you could override selected document class member functions and
the File Open menu item would bring up a list of databases instead of a list of files.
Chapter Three
9. Getting Started with AppWizard—"Hello, world!"
Chapter 2 sketched the MFC library version 6.0 document-view architecture. This hands-on chapter shows you how
to build a functioning MFC library application, but it insulates you from the complexities of the class hierarchy and
object interrelationships. You'll work with only one document-view program element, the "view class" that is closely
associated with a window. For the time being, you can ignore elements such as the application class, the frame
window, and the document. Of course, your application won't be able to save its data on disk, and it won't support
multiple views, but Part III of this book provides plenty of opportunity to exploit those features.
Because resources are so important in Microsoft Windows-based applications, you'll use ResourceView to visually
explore the resources of your new program. You'll also get some hints for setting up your Windows environment for
maximum build speed and optimal debugging output.
Requirements:
To compile and run the examples presented in this chapter and in the following chapters, you must
have successfully installed the released version of Microsoft Windows 95 or Microsoft Windows NT
version 4.0 or later, plus all the Microsoft Visual C++ version 6.0 components. Be sure that Visual
C++'s executable, include, and library directories are set correctly. (You can change the directories
by choosing Options from the Tools menu.) If you have any problems with the following steps,
please refer to your Visual C++ documentation and Readme files for troubleshooting instructions.
10. What's a View?
From a user's standpoint, a view is an ordinary window that the user can size, move, and close in the same way as
any other Windows-based application window. From the programmer's perspective, a view is a C++ object of a class
derived from the MFC library CView class. Like any C++ object, the view object's behavior is determined by the
member functions (and data members) of the class—both the application-specific functions in the derived class and
the standard functions inherited from the base classes.
With Visual C++, you can produce interesting applications for Windows by simply adding code to the derived view
class that the AppWizard code generator produces. When your program runs, the MFC library application framework
constructs an object of the derived view class and displays a window that is tightly linked to the C++ view object. As
is customary in C++ programming, the view class code is divided into two source modules—the header file (H) and
the implementation file (CPP).
- 14 -

11. Single Document Interface vs. Multiple Document Interface


The MFC library supports two distinct application types: Single Document Interface (SDI) and Multiple Document
Interface (MDI). An SDI application has, from the user's point of view, only one window. If the application depends on
disk-file "documents," only one document can be loaded at a time. The original Windows Notepad is an example of
an SDI application. An MDI application has multiple child windows, each of which corresponds to an individual
document. Microsoft Word is a good example of an MDI application.
When you run AppWizard to create a new project, MDI is the default application type. For the early examples in this
book, you'll be generating SDI applications because fewer classes and features are involved. Be sure you select the
Single Document option (on the first AppWizard screen) for these examples. Starting with Chapter 18, you'll be
generating MDI applications. The MFC library application framework architecture ensures that most SDI examples
can be upgraded easily to MDI applications.
12. The "Do-Nothing" Application—EX03A
The AppWizard tool generates the code for a functioning MFC library application. This working application simply
brings up an empty window with a menu attached. Later you'll add code that draws inside the window. Follow these
steps to build the application:
Run AppWizard to generate SDI application source code. Choose New from Visual C++'s File menu, and then
click the Projects tab in the resulting New dialog box, as shown here.

Make sure that MFC AppWizard (exe) is highlighted, and then type C:\vcpp32\ in the Location edit box. Type
ex03a as shown in the Project Name edit box, and then click the OK button. Now you will step through a
sequence of AppWizard screens, the first of which is shown here.
- 15 -

Be sure to select the Single Document option. Accept the defaults in the next four screens. The last screen
should look like the following illustration.

Notice that the class names and source-file names have been generated based on the project name EX03A.
You could make changes to these names at this point if you wanted to. Click the Finish button. Just before
AppWizard generates your code, it displays the New Project Information dialog box, shown here.

When you click the OK button, AppWizard begins to create your application's subdirectory (ex03a under
\vcpp32) and a series of files in that subdirectory. When AppWizard is finished, look in the application's
subdirectory. The following files are of interest (for now).
File Description

ex03a.dsp A project file that allows Visual C++ to build your application
- 16 -

ex03a.dsw A workspace file that contains a single entry for ex03a.dsp

ex03a.rc An ASCII resource script file

ex03aView.cpp A view class implementation file that contains CEx03aView class member functions

ex03aView.h A view class header file that contains the CEx03aView class declaration

ex03a.opt A binary file that tells Visual C++ which files are open for this project and how the windows
are arranged (This file is not created until you save the project.)

ReadMe.txt A text file that explains the purpose of the generated files

resource.h A header file that contains #define constant definitions


Open the ex03aView.cpp and ex03aView.h files and look at the source code. Together these files define the
CEx03aView class, which is central to the application. An object of class CEx03aView corresponds to the
application's view window, where all the "action" takes place.
Compile and link the generated code. AppWizard, in addition to generating code, creates custom project and
workspace files for your application. The project file, ex03a.dsp, specifies all the file dependencies together with
the compile and link option flags. Because the new project becomes Visual C++'s current project, you can now
build the application by choosing Build Ex03a.exe from the Build menu or by clicking the Build toolbar button,
shown here.

If the build is successful, an executable program named ex03a.exe is created in a new Debug subdirectory
underneath \vcpp32\ex03a. The OBJ files and other intermediate files are also stored in Debug. Compare the
file structure on disk with the structure in the Workspace window's FileView page shown here.

The FileView page contains a logical view of your project. The header files show up under Header Files, even
though they are in the same subdirectory as the CPP files. The resource files are stored in the \res
subdirectory.
Test the resulting application. Choose Execute Ex03a.exe from the Build menu. Experiment with the program. It
doesn't do much, does it? (What do you expect for no coding?) Actually, as you might guess, the program has
a lot of features—you simply haven't activated them yet. Close the program window when you've finished
experimenting.
- 17 -

Browse the application. Choose Source Browser from the Tools menu. If your project settings don't specify
browser database creation, Visual C++ will offer to change the settings and recompile the program for you. (To
change the settings yourself, choose Settings from the Project menu. On the C/C++ page, click Generate
Browse Info, and on the Browse Info page, click Build Browse Info File.)
When the Browse window appears, choose Base Classes And Members and then type CEx03aView. After you
expand the hierarchy, you should see output similar to this.

Compare the browser output to ClassView in the Workspace window.

ClassView doesn't show the class hierarchy, but it also doesn't involve the extra overhead of the browser. If
ClassView is sufficient for you, don't bother building the browser database.
13. The CEx03aView View Class
AppWizard generated the CEx03aView view class, and this class is specific to the EX03A application. (AppWizard
generates classes based on the project name you entered in the first AppWizard dialog box.) CEx03aView is at the
bottom of a long inheritance chain of MFC library classes, as illustrated previously in the Browse window. The class
picks up member functions and data members all along the chain. You can learn about these classes in the Microsoft
- 18 -

Foundation Class Reference (online or printed version), but you must be sure to look at the descriptions for every
base class because the descriptions of inherited member functions aren't generally repeated for derived classes.
The most important CEx03aView base classes are CWnd and CView. CWnd provides CEx03aView's "windowness,"
and CView provides the hooks to the rest of the application framework, particularly to the document and to the frame
window, as you'll see in Part III of this book.
14. Drawing Inside the View Window—The Windows Graphics Device Interface
Now you're ready to write code to draw inside the view window. You'll be making a few changes directly to the
EX03A source code.
14.1 The OnDraw Member Function
Specifically, you'll be fleshing out OnDraw in ex03aView.cpp. OnDraw is a virtual member function of the CView
class that the application framework calls every time the view window needs to be repainted. A window needs to be
repainted if the user resizes the window or reveals a previously hidden part of the window, or if the application
changes the window's data. If the user resizes the window or reveals a hidden area, the application framework calls
OnDraw, but if a function in your program changes the data, it must inform Windows of the change by calling the
view's inherited Invalidate (or InvalidateRect) member function. This call to Invalidate triggers a later call to OnDraw.
Even though you can draw inside a window at any time, it's recommended that you let window changes accumulate
and then process them all together in the OnDraw function. That way your program can respond both to program-
generated events and to Windows-generated events such as size changes.
14.2 The Windows Device Context
Recall from Chapter 1 that Windows doesn't allow direct access to the display hardware but communicates through
an abstraction called a "device context" that is associated with the window. In the MFC library, the device context is a
C++ object of class CDC that is passed (by pointer) as a parameter to OnDraw. After you have the device context
pointer, you can call the many CDC member functions that do the work of drawing.
14.3 Adding Draw Code to the EX03A Program
Now let's write the code to draw some text and a circle inside the view window. Be sure that the project EX03A is
open in Visual C++. You can use the Workspace window's ClassView to locate the code for the function (double-click
on OnDraw), or you can open the source code file ex03aView.cpp from FileView and locate the function yourself.
Edit the OnDraw function in ex03aView.cpp. Find the AppWizard-generated OnDraw function in
ex03aView.cpp:
void CEx03aView::OnDraw(CDC* pDC)
{
CEx03aDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

// TODO: add draw code for native data here


}
The following boldface code (which you type in) replaces the previous code:
void CEx03aView::OnDraw(CDC* pDC)
{
pDC->TextOut(0, 0, "Hello, world!"); // prints in default font
// & size, top left corner
pDC->SelectStockObject(GRAY_BRUSH); // selects a brush for the
// circle interior
pDC->Ellipse(CRect(0, 20, 100, 120)); // draws a gray circle
// 100 units in diameter
}
You can safely remove the call to GetDocument because we're not dealing with documents yet. The functions
TextOut, SelectStockObject, and Ellipse are all member functions of the application framework's device context
class CDC. The Ellipse function draws a circle if the bounding rectangle's length is equal to its width.
The MFC library provides a handy utility class, CRect, for Windows rectangles. A temporary CRect object
serves as the bounding rectangle argument for the ellipse drawing function. You'll see more of the CRect class
in quite a few of the examples in this book.
Recompile and test EX03A. Choose Build from the Project menu, and, if there are no compile errors, test the
application again. Now you have a program that visibly does something!
For Win32 Programmers
Rest assured that the standard Windows WinMain and window procedure functions are hidden away
inside the application framework. You'll see those functions later in this book, when the MFC library
- 19 -

frame and application classes are examined. In the meantime, you're probably wondering what
happened to the WM_PAINT message, aren't you? You would expect to do your window drawing in
response to this Windows message, and you would expect to get your device context handle from a
PAINTSTRUCT structure returned by the Windows BeginPaint function.
It so happens that the application framework has done all the dirty work for you and served up a
device context (in object pointer form) in the virtual function OnDraw. As explained in Chapter 2, true
virtual functions in window classes are an MFC library rarity. MFC library message map functions
dispatched by the application framework handle most Windows messages. MFC version 1.0
programmers always defined an OnPaint message map function for their derived window classes.
Beginning with version 2.5, however, OnPaint was mapped in the CView class, and that function
made a polymorphic call to OnDraw. Why? Because OnDraw needs to support the printer as well as
the display. Both OnPaint and OnPrint call OnDraw, thus enabling the same drawing code to
accommodate both the printer and the display.
15. A Preview of the Resource Editors
Now that you have a complete application program, it's a good time for a quick look at the resource editors. Although
the application's resource script, ex03a.rc, is an ASCII file, modifying it with a text editor is not a good idea. That's the
resource editors' job.
15.1 The Contents of ex03a.rc
The resource file determines much of the EX03A application's "look and feel." The file ex03a.rc contains (or points
to) the Windows resources listed here.
Resource Description
Accelerator Definitions for keys that simulate menu and toolbar selections.

Dialog Layout and contents of dialog boxes. EX03A has only the About dialog box.

Icon Icons (16-by-16-pixel and 32-by-32-pixel versions), such as the application icon you see in
Microsoft Windows Explorer and in the application's About dialog box. EX03A uses the MFC
logo for its application icon.

Menu The application's top-level menu and associated pop-up menus.

String table Strings that are not part of the C++ source code.

Toolbar The row of buttons immediately below the menu.

Version Program description, version number, language, and so on.


In addition to the resources listed above, ex03a.rc contains the statements

#include "afxres.h"
#include "afxres.rc"
which bring in some MFC library resources common to all applications. These resources include strings, graphical
buttons, and elements needed for printing and OLE.

If you're using the shared DLL version of the MFC library, the common resources are stored inside
the MFC DLL.
The ex03a.rc file also contains the statement

#include "resource.h"
This statement brings in the application's three #define constants, which are IDR_MAINFRAME (identifying the
menu, icon, string list, and accelerator table), IDR_EX03ATYPE (identifying the default document icon, which we
won't use in this program), and IDD_ABOUTBOX (identifying the About dialog box). This same resource.h file is
included indirectly by the application's source code files. If you use a resource editor to add more constants
(symbols), the definitions ultimately show up in resource.h. Be careful if you edit this file in text mode because your
changes might be removed the next time you use a resource editor.
15.2 Running the Dialog Resource Editor
Open the project's RC file. Click the ResourceView button in the Workspace window. If you expand each item,
you will see the following in the resource editor window.
- 20 -

Examine the application's resources. Now take some time to explore the individual resources. When you select
a resource by double-clicking on it, another window opens with tools appropriate for the selected resource. If
you open a dialog resource, the control palette should appear. If it doesn't, right-click inside any toolbar, and
then check Controls.
Modify the IDD_ABOUTBOX dialog box. Make some changes to the About Ex03a dialog box, shown here.

You can change the size of the window by dragging the right and bottom borders, move the OK button, change
the text, and so forth. Simply click on an element to select it, and then right-click to change its properties.
Rebuild the project with the modified resource file. In Visual C++, choose Build Ex03a.exe from the Build menu.
Notice that no actual C++ recompilation is necessary. Visual C++ saves the edited resource file, and then the
Resource Compiler (rc.exe) processes ex03a.rc to produce a compiled version, ex03a.res, which is fed to the
linker. The linker runs quickly because it can link the project incrementally.
Test the new version of the application. Run the EX03A program again, and then choose About from the
application's Help menu to confirm that your dialog box was changed as expected.
16. Win32 Debug Target vs. Win32 Release Target
If you open the drop-down list on the Build toolbar, you'll notice two items: Win32 Debug and Win32 Release. (The
Build toolbar is not present by default, but you can choose Customize from the Tools menu to display it.) These items
are targets that represent distinct sets of build options. When AppWizard generates a project, it creates two default
targets with different settings. These settings are summarized in the following table.
Option Release Build Debug Build
- 21 -

Source code Disabled Enabled for both compiler and


debugging linker

MFC diagnostic Disabled (NDEBUG defined) Enabled (_DEBUG defined)


macros

Library linkage MFC Release library MFC Debug libraries

Compiler optimization Speed optimization (not available in Learning No optimization (faster compile)
Edition)
You develop your application in Debug mode, and then you rebuild in Release mode prior to delivery. The Release
build EXE will be smaller and faster, assuming that you have fixed all the bugs. You select the configuration from the
build target window in the Build toolbar, as shown in Figure 1-2 in Chapter 1. By default, the Debug output files and
intermediate files are stored in the project's Debug subdirectory; the Release files are stored in the Release
subdirectory. You can change these directories on the General tab in the Project Settings dialog box.
You can create your own custom configurations if you need to by choosing Configurations from Visual C++'s Build
menu.
17. Enabling the Diagnostic Macros
The application framework TRACE macros are particularly useful for monitoring program activity. They require that
tracing be enabled, which is the default setting. If you're not seeing TRACE output from your program, first make
sure that you are running the debug target from the debugger and then run the TRACER utility. If you check the
Enable Tracing checkbox, TRACER will insert the statement
TraceEnabled = 1
in the [Diagnostics] section of a file named Afx.ini. (No, it's not stored in the Registry.) You can also use TRACER to
enable other MFC diagnostic outputs, including message, OLE, database, and Internet information.
18. Understanding Precompiled Headers
When AppWizard generates a project, it generates switch settings and files for precompiled headers. You must
understand how the make system processes precompiled headers in order to manage your projects effectively.

Visual C++ has two precompiled header "systems:" automatic and manual. Automatic precompiled
headers, activated with the /Yx compiler switch, store compiler output in a "database" file. Manual
precompiled headers are activated by the /Yc and /Yu switch settings and are central to all
AppWizard-generated projects.
Precompiled headers represent compiler "snapshots" taken at a particular line of source code. In MFC library
programs, the snapshot is generally taken immediately after the following statement:
#include "StdAfx.h"
The file StdAfx.h contains #include statements for the MFC library header files. The file's contents depend on the
options that you select when you run AppWizard, but the file always contains these statements:
#include <afxwin.h>
#include <afxext.h>
If you're using compound documents, StdAfx.h also contains the statement
#include <afxole.h>
and if you're using Automation or ActiveX Controls, it contains
#include <afxdisp.h>
If you're using Internet Explorer 4 Common Controls, StdAfx.h contains the statement
#include <afxdtctl.h>
Occasionally you will need other header files—for example, the header for template-based collection classes that is
accessed by the statement
#include <afxtempl.h>
The source file StdAfx.cpp contains only the statement
#include "StdAfx.h"
and is used to generate the precompiled header file in the project directory. The MFC library headers included by
StdAfx.h never change, but they do take a long time to compile. The compiler switch /Yc, used only with StdAfx.cpp,
causes creation of the precompiled header (PCH) file. The switch /Yu, used with all the other source code files,
causes use of an existing PCH file. The switch /Fp specifies the PCH filename that would otherwise default to the
project name (with the PCH extension) in the target's output files subdirectory. Figure 3-1 illustrates the whole
process.
- 22 -

AppWizard sets the /Yc and /Yu switches for you, but you can make changes if you need to. It's possible to define
compiler switch settings for individual source files. On the C/C++ tab in the Project Settings dialog box, if you select
only StdAfx.cpp, you'll see the /Yc setting. This overrides the /Yu setting that is defined for the target.
Be aware that PCH files are big—5 MB is typical. If you're not careful, you'll fill up your hard disk. You can keep
things under control by periodically cleaning out your projects' Debug directories, or you can use the /Fp compiler
option to reroute PCH files to a common directory.

Figure 3-1. The Visual C++ precompiled header process.


19. Two Ways to Run a Program
Visual C++ lets you run your program directly (by pressing Ctrl-F5) or through the debugger (by pressing F5).
Running your program directly is much faster because Visual C++ doesn't have to load the debugger first. If you
know you don't want to see diagnostic messages or use breakpoints, start your program by pressing Ctrl-F5 or use
the "exclamation point" button on the Build toolbar.
Chapter Four
20. Basic Event Handling, Mapping Modes, and a Scrolling View
In Chapter 3, you saw how the Microsoft Foundation Class (MFC) Library application framework called the view
class's virtual OnDraw function. Take a look at the online help for the MFC library now. If you look at the
documentation for the CView class and its base class, CWnd, you'll see several hundred member functions.
Functions whose names begin with On—such as OnKeyDown and OnLButtonUp—are member functions that the
application framework calls in response to various Windows "events" such as keystrokes and mouse clicks.
Most of these application framework-called functions, such as OnKeyDown, aren't virtual functions and thus require
more programming steps. This chapter explains how to use the Visual C++ ClassWizard to set up the message map
structure necessary for connecting the application framework to your functions' code. You'll see the practical
application of message map functions.
The first two examples use an ordinary CView class. In EX04A, you'll learn about the interaction between user-driven
events and the OnDraw function. In EX04B, you'll see the effects of different Windows mapping modes.
More often than not, you'll want a scrolling view. The last example, EX04C, uses CScrollView in place of the CView
base class. This allows the MFC library application framework to insert scroll bars and connect them to the view.
21. Getting User Input—Message Map Functions
Your EX03A application from Chapter 3 did not accept user input (other than the standard Microsoft Windows
resizing and window close commands). The window contained menus and a toolbar, but these were not "connected"
to the view code. The menus and the toolbar won't be discussed until Part III of this book because they depend on
the frame class, but plenty of other Windows input sources will keep you busy until then. Before you can process any
Windows event, even a mouse click, however, you must learn how to use the MFC message map system.
21.1 The Message Map
When the user presses the left mouse button in a view window, Windows sends a message—specifically
WM_LBUTTONDOWN—to that window. If your program needs to take action in response to WM_LBUTTONDOWN,
your view class must have a member function that looks like this:
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
- 23 -

{
// event processing code here
}
Your class header file must also have the corresponding prototype:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
The afx_msg notation is a "no-op" that alerts you that this is a prototype for a message map function. Next, your
code file needs a message map macro that connects your OnLButtonDown function to the application framework:
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_WM_LBUTTONDOWN() // entry specifically for OnLButtonDown
// other message map entries
END_MESSAGE_MAP()
Finally, your class header file needs the statement
DECLARE_MESSAGE_MAP()
How do you know which function goes with which Windows message? Appendix A (and the MFC library online
documentation) includes a table that lists all standard Windows messages and corresponding member function
prototypes. You can manually code the message-handling functions—indeed, that is still necessary for certain
messages. Fortunately, Visual C++ provides a tool, ClassWizard, that automates the coding of most message map
functions.
21.2 Saving the View's State—Class Data Members
If your program accepts user input, you'll want the user to have some visual feedback. The view's OnDraw function
draws an image based on the view's current "state," and user actions can alter that state. In a full-blown MFC
application, the document object holds the state of the application, but you're not to that point yet. For now, you'll use
two view class data members, m_rectEllipse and m_nColor. The first is an object of class CRect, which holds the
current bounding rectangle of an ellipse, and the second is an integer that holds the current ellipse color value.

By convention, MFC library nonstatic class data member names begin with m_.
You'll make a message-mapped member function toggle the ellipse color (the view's state) between gray and white.
(The toggle is activated by pressing the left mouse button.) The initial values of m_rectEllipse and m_nColor are set
in the view's constructor, and the color is changed in the OnLButtonDown member function.

Why not use a global variable for the view's state? Because if you did, you'd be in trouble if your
application had multiple views. Besides, encapsulating data in objects is a big part of what object-
oriented programming is all about.
21.2.1 Initializing a View Class Data Member
The most efficient place to initialize a class data member is in the constructor, like this:
CMyView::CMyView() : m_rectEllipse(0, 0, 200, 200) {...}
You could initialize m_nColor with the same syntax. Because we're using a built-in type (integer), the generated code
is the same if you use an assignment statement in the constructor body.
21.3 Invalid Rectangle Theory
The OnLButtonDown function could toggle the value of m_nColor all day, but if that's all it did, the OnDraw function
wouldn't get called (unless, for example, the user resized the view window). The OnLButtonDown function must call
the InvalidateRect function (a member function that the view class inherits from CWnd). InvalidateRect triggers a
Windows WM_PAINT message, which is mapped in the CView class to call to the virtual OnDraw function. If
necessary, OnDraw can access the "invalid rectangle" parameter that was passed to InvalidateRect.
There are two ways to optimize painting in Windows. First of all, you must be aware that Windows updates only
those pixels that are inside the invalid rectangle. Thus, the smaller you make the invalid rectangle (in the
OnLButtonDown handler, for instance), the quicker it can be repainted. Second, it's a waste of time to execute
drawing instructions outside the invalid rectangle. Your OnDraw function could call the CDC member function
GetClipBox to determine the invalid rectangle, and then it could avoid drawing objects outside it. Remember that
OnDraw is being called not only in response to your InvalidateRect call but also when the user resizes or exposes
the window. Thus, OnDraw is responsible for all drawing in a window, and it has to adapt to whatever invalid
rectangle it gets.
For Win32 Programmers
The MFC library makes it easy to attach your own state variables to a window through C++ class
data members. In Win32 programming, the WNDCLASS members cbClsExtra and cbWndExtra are
available for this purpose, but the code for using this mechanism is so complex that developers tend
to use global variables instead.
- 24 -

21.4 The Window's Client Area


A window has a rectangular client area that excludes the border, caption bar, menu bar, and any docking toolbars.
The CWnd member function GetClientRect supplies you with the client-area dimensions. Normally, you're not
allowed to draw outside the client area, and most mouse messages are received only when the mouse cursor is in
the client area.
21.5 CRect, CPoint, and CSize Arithmetic
The CRect, CPoint, and CSize classes are derived from the Windows RECT, POINT, and SIZE structures, and thus
they inherit public integer data members as follows:
CRect left, top, right, bottom

CPoint x, y

CSize cx, cy
If you look in the Microsoft Foundation Class Reference, you will see that these three classes have a number of
overloaded operators. You can, among other things, do the following:
Add a CSize object to a CPoint object
Subtract a CSize object from a CPoint object
Subtract one CPoint object from another, yielding a CSize object
Add a CPoint or CSize object to a CRect object
Subtract a CPoint or CSize object from a CRect object
The CRect class has member functions that relate to the CSize and CPoint classes. For example, the TopLeft
member function returns a CPoint object, and the Size member function returns a CSize object. From this, you can
begin to see that a CSize object is the "difference between two CPoint objects" and that you can "bias" a CRect
object by a CPoint object.
21.6 Is a Point Inside a Rectangle?
The CRect class has a member function PtInRect that tests a point to see whether it falls inside a rectangle. The
second OnLButtonDown parameter (point) is an object of class CPoint that represents the cursor location in the
client area of the window. If you want to know whether that point is inside the m_rectEllipse rectangle, you can use
PtInRect in this way:
if (m_rectEllipse.PtInRect(point)) {
// point is inside rectangle
}
As you'll soon see, however, this simple logic applies only if you're working in device coordinates (which you are at
this stage).
21.7 The CRect LPCRECT Operator
If you read the Microsoft Foundation Class Reference carefully, you will notice that CWnd::InvalidateRect takes an
LPCRECT parameter (a pointer to a RECT structure), not a CRect parameter. A CRect parameter is allowed
because the CRect class defines an overloaded operator, LPCRECT(), that returns the address of a CRect object,
which is equivalent to the address of a RECT object. Thus, the compiler converts CRect arguments to LPCRECT
arguments when necessary. You call functions as though they had CRect reference parameters. The view member
function code
CRect rectClient;
GetClientRect(rectClient);
retrieves the client rectangle coordinates and stores them in rectClient.
21.8 Is a Point Inside an Ellipse?
The EX04A code determines whether the mouse hit is inside the rectangle. If you want to make a better test, you can
find out whether the hit is inside the ellipse. To do this, you must construct an object of class CRgn that corresponds
to the ellipse and then use the PtInRegion function instead of PtInRect. Here's the code:
CRgn rgn;
rgn.CreateEllipticRgnIndirect(m_rectEllipse);
if (rgn.PtInRegion(point)) {
// point is inside ellipse
}
Note that the CreateEllipticRgnIndirect function is another function that takes an LPCRECT parameter. It builds a
special region structure within Windows that represents an elliptical region inside a window. That structure is then
attached to the C++ CRgn object in your program. (The same type of structure can also represent a polygon.)
21.9 The EX04A Example
- 25 -

In the EX04A example, an ellipse (which happens to be a circle) changes color when the user presses the left mouse
button while the mouse cursor is inside the rectangle that bounds the ellipse. You'll use the view class data members
to hold the view's state, and you'll use the InvalidateRect function to cause the view to be redrawn.
In the Chapter 3 example, drawing in the window depended on only one function, OnDraw. The EX04A example
requires three customized functions (including the constructor) and two data members. The complete CEx04aView
header and source code files are listed in Figure 4-1. (The steps for creating the program are shown after the
program listings.) All changes to the original AppWizard and OnLButtonDown ClassWizard output are in boldface.
21.9.1 EX04AVIEW.H
// ex04aView.h : interface of the CEx04aView class
//
///////////////////////////////////////////////////////////////////////

#if !defined(AFX_EX04AVIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2
__INCLUDED_)
#define AFX_EX04AVIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2
__INCLUDED_

#if _MFC_VER > 1000


#pragma once
#endif // _MFC_VER > 1000
class CEx04aView : public CView
{
protected: // create from serialization only
CEx04aView();
DECLARE_DYNCREATE(CEx04aView)

// Attributes
public:
CEx04aDoc* GetDocument();

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CEx04aView)
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CEx04aView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:
- 26 -

// Generated message map functions


protected:
//{{AFX_MSG(CEx04aView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
private:
int m_nColor;
CRect m_rectEllipse;
};
#ifndef _DEBUG // debug version in ex04aView.cpp
inline CEx04aDoc* CEx04aView::GetDocument()
{ return (CEx04aDoc*)m_pDocument; }
#endif

///////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations
// immediately before the previous line.

#endif // !defined(AFX_EX04AVIEW_H__B188BE41_6377_11D0_8FD4_00C04FC2A0C2__INCLUDED_)
21.9.2 EX04AVIEW.CPP
// ex04aView.cpp : implementation of the CEx04aView class
//

#include "stdafx.h"
#include "ex04a.h"

#include "ex04aDoc.h"
#include "ex04aView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE
__;
#endif

///////////////////////////////////////////////////////////////////////
// CEx04aView

IMPLEMENT_DYNCREATE(CEx04aView, CView)

BEGIN_MESSAGE_MAP(CEx04aView, CView)
//{{AFX_MSG_MAP(CEx04aView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////
// CEx04aView construction/destruction
- 27 -

CEx04aView::CEx04aView() : m_rectEllipse(0, 0, 200, 200)


{
m_nColor = GRAY_BRUSH;
}

CEx04aView::~CEx04aView()
{
}

BOOL CEx04aView::PreCreateWindow(CREATESTRUCT& cs)


{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs

return CView::PreCreateWindow(cs);
}

///////////////////////////////////////////////////////////////////////
// CEx04aView drawing

void CEx04aView::OnDraw(CDC* pDC)


{
pDC->SelectStockObject(m_nColor);
pDC->Ellipse(m_rectEllipse);
}

///////////////////////////////////////////////////////////////////////
// CEx04aView printing

BOOL CEx04aView::OnPreparePrinting(CPrintInfo* pInfo)


{
// default preparation
return DoPreparePrinting(pInfo);
}

void CEx04aView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)


{
// TODO: add extra initialization before printing
}

void CEx04aView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)


{
// TODO: add cleanup after printing
}

///////////////////////////////////////////////////////////////////////
// CEx04aView diagnostics

#ifdef _DEBUG
void CEx04aView::AssertValid() const
{
CView::AssertValid();
}
- 28 -

void CEx04aView::Dump(CDumpContext& dc) const


{
CView::Dump(dc);
}

CEx04aDoc* CEx04aView::GetDocument() // non-debug version is inline


{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx04aDoc)));
return (CEx04aDoc*)m_pDocument;
}
#endif //_DEBUG

///////////////////////////////////////////////////////////////////////
// CEx04aView message handlers

void CEx04aView::OnLButtonDown(UINT nFlags, CPoint point)


{
if (m_rectEllipse.PtInRect(point)) {
if (m_nColor == GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect(m_rectEllipse);
}
}
Figure 4-1. The CEx04aView header and source code files.
21.10 Using ClassWizard with EX04A
Look at the following ex04aView.h source code:
//{{AFX_MSG(CEx04aView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
Now look at the following ex04aView.cpp source code:
//{{AFX_MSG_MAP(CEx04aView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
AppWizard generated the funny-looking comment lines for the benefit of ClassWizard. ClassWizard adds message
handler prototypes between the AFX_MSG brackets and message map entries between the AFX_MSG_MAP
brackets. In addition, ClassWizard generates a skeleton OnLButtonDown member function in ex04aView.cpp,
complete with the correct parameter declarations and return type.
Notice how the AppWizard_ClassWizard combination is different from a conventional code generator. You run a
conventional code generator only once and then edit the resulting code. You run AppWizard to generate the
application only once, but you can run ClassWizard as many times as necessary, and you can edit the code at any
time. You're safe as long as you don't alter what's inside the AFX_MSG and AFX_MSG_MAP brackets.
21.10.1 Using AppWizard and ClassWizard Together
The following steps show how you use AppWizard and ClassWizard together to create this application:
Run AppWizard to create EX04A. Use AppWizard to generate an SDI project named EX04A in the
\vcpp32\ex04a subdirectory. The options and the default class names are shown here.
- 29 -

Add the m_rectEllipse and m_nColor data members to CEx04aView. With the Workspace window set to
ClassView, right-click the CEx04aView class, select Add Member Variable, and then insert the following two
data members:
private:
CRect m_rectEllipse;
int m_nColor;
If you prefer, you could type the above code inside the class declaration in the file ex04aView.h.
Use ClassWizard to add a CEx04aView class message handler. Choose ClassWizard from the View menu of
Visual C++, or right-click inside a source code window and choose ClassWizard from the context menu. When
the MFC ClassWizard dialog appears, be sure that the CEx04aView class is selected, as shown in the
illustration below. Now click on CEx04aView at the top of the Object IDs list box, and then scroll down past the
virtual functions in the Messages list box and double-click on WM_LBUTTONDOWN. The OnLButtonDown
function name should appear in the Member Functions list box, and the message name should be displayed in
bold in the Messages list box. Here's the ClassWizard dialog box.
- 30 -

Instead of using ClassWizard, you can map the function from the Visual C++ WizardBar
(shown in Figure 1-2 in Chapter 1).
Edit the OnLButtonDown code in ex04aView.cpp. Click the Edit Code button. ClassWizard opens an edit
window for ex04aView.cpp in Visual C++ and positions the cursor on the newly generated OnLButtonDown
member function. The following boldface code (that you type in) replaces the previous code:
void CEx04aView::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_rectEllipse.PtInRect(point)) {
if (m_nColor
== GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect(m_rectEllipse);
}
}
Edit the constructor and the OnDraw function in ex04aView.cpp. The following boldface code (that you type in)
replaces the previous code:
CEx04aView::CEx04aView() : m_rectEllipse(0, 0, 200, 200)
{
m_nColor = GRAY_BRUSH;
}
.
.
.
void CEx04aView::OnDraw(CDC* pDC)
{
pDC->SelectStockObject(m_nColor);
pDC->Ellipse(m_rectEllipse);
}
Build and run the EX04A program. Choose Build Ex04a.exe from the Build menu, or, on the Build toolbar, click
the button shown here.
- 31 -

Then choose Execute Ex04a.exe from the Build menu. The resulting program responds to presses of the left
mouse button by changing the color of the circle in the view window. (Don't press the mouse's left button
quickly in succession; Windows interprets this as a double click rather than two single clicks.)
For Win32 Programmers
A conventional Windows-based application registers a series of window classes (not the same as
C++ classes) and, in the process, assigns a unique function, known as a window procedure, to each
class. Each time the application calls CreateWindow to create a window, it specifies a window class
as a parameter and thus links the newly created window to a window procedure function. This
function, called each time Windows sends a message to the window, tests the message code that is
passed as a parameter and then executes the appropriate code to handle the message.
The MFC application framework has a single window class and window procedure function for most
window types. This window procedure function looks up the window handle (passed as a parameter)
in the MFC handle map to get the corresponding C++ window object pointer. The window procedure
function then uses the MFC runtime class system (see Appendix B) to determine the C++ class of
the window object. Next it locates the handler function in static tables created by the dispatch map
functions, and finally it calls the handler function with the correct window object selected.
22. Mapping Modes
Up to now, your drawing units have been display pixels, also known as device coordinates. The EX04A drawing units
are pixels because the device context has the default mapping mode, MM_TEXT, assigned to it. The statement
pDC->Rectangle(CRect(0, 0, 200, 200));
draws a square of 200-by-200 pixels, with its top-left corner at the top left of the window's client area. (Positive y
values increase as you move down the window.) This square would look smaller on a high-resolution display of
1024-by-768 pixels than it would look on a standard VGA display that is 640-by-480 pixels, and it would look tiny if
printed on a laser printer with 600-dpi resolution. (Try EX04A's Print Preview feature to see for yourself.)
What if you want the square to be 4-by-4 centimeters (cm), regardless of the display device? Windows provides a
number of other mapping modes, or coordinate systems, that can be associated with the device context. Coordinates
in the current mapping mode are called logical coordinates. If you assign the MM_HIMETRIC mapping mode, for
example, a logical unit is 1/100 millimeter (mm) instead of 1 pixel. In the MM_HIMETRIC mapping mode, the y axis
runs in the opposite direction to that in the MM_TEXT mode: y values decrease as you move down. Thus, a 4-by-4-
cm square is drawn in logical coordinates this way:
pDC->Rectangle(CRect(0, 0, 4000, -4000));
Looks easy, doesn't it? Well, it isn't, because you can't work only in logical coordinates. Your program is always
switching between device coordinates and logical coordinates, and you need to know when to convert between
them. This section gives you a few rules that could make your programming life easier. First you need to know what
mapping modes Windows gives you.
22.1 The MM_TEXT Mapping Mode
At first glance, MM_TEXT appears to be no mapping mode at all, but rather another name for device coordinates.
Almost. In MM_TEXT, coordinates map to pixels, values of x increase as you move right, and values of y increase as
you move down, but you're allowed to change the origin through calls to the CDC functions SetViewportOrg and
SetWindowOrg. Here's some code that sets the window origin to (100, 100) in logical coordinate space and then
draws a 200-by-200-pixel square offset by (100, 100). (An illustration of the output is shown in Figure 4-2.) The
logical point (100, 100) maps to the device point (0, 0). A scrolling window uses this kind of transformation.
void CMyView::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_TEXT);
pDC->SetWindowOrg(CPoint(100, 100));
pDC->Rectangle(CRect(100, 100, 300, 300));
}
- 32 -

Figure 4-2. A square drawn after the origin has been moved to (100, 100).
22.2 The Fixed-Scale Mapping Modes
One important group of Windows mapping modes provides fixed scaling. You have already seen that, in the
MM_HIMETRIC mapping mode, x values increase as you move right and y values decrease as you move down. All
fixed mapping modes follow this convention, and you can't change it. The only difference among the fixed mapping
modes is the actual scale factor, listed in the table shown here.
Mapping Mode Logical Unit

MM_LOENGLISH 0.01 inch

MM_HIENGLISH 0.001 inch

MM_LOMETRIC 0.1 mm

MM_HIMETRIC 0.01 mm
1
MM_TWIPS /1440 inch
The last mapping mode, MM_TWIPS, is most often used with printers. One twip unit is 1/20 point. (A point is a type
measurement unit. In Windows it equals exactly 1/72 inch.) If the mapping mode is MM_TWIPS and you want, for
example, 12-point type, set the character height to 12 × 20, or 240, twips.
22.3 The Variable-Scale Mapping Modes
Windows provides two mapping modes, MM_ISOTROPIC and MM_ANISOTROPIC, that allow you to change the
scale factor as well as the origin. With these mapping modes, your drawing can change size as the user changes the
size of the window. Also, if you invert the scale of one axis, you can "flip" an image about the other axis and you can
define your own arbitrary fixed-scale factors.
With the MM_ISOTROPIC mode, a 1:1 aspect ratio is always preserved. In other words, a circle is always a circle as
the scale factor changes. With the MM_ANISOTROPIC mode, the x and y scale factors can change independently.
Circles can be squished into ellipses.
Here's an OnDraw function that draws an ellipse that fits exactly in its window:
void CMyView::OnDraw(CDC* pDC)
{
CRect rectClient;

GetClientRect(rectClient);
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(1000, 1000);
pDC->SetViewportExt(rectClient.right, -rectClient.bottom);
pDC->SetViewportOrg(rectClient.right / 2, rectClient.bottom / 2);

pDC->Ellipse(CRect(-500, -500, 500, 500));


}
What's going on here? The functions SetWindowExt and SetViewportExt work together to set the scale, based on
the window's current client rectangle returned by the GetClientRect function. The resulting window size is exactly
1000-by-1000 logical units. The SetViewportOrg function sets the origin to the center of the window. Thus, a
centered ellipse with a radius of 500 logical units fills the window exactly, as illustrated in Figure 4-3.
- 33 -

Figure 4-3. A centered ellipse drawn in the MM_ANISOTROPIC mapping mode.


Here are the formulas for converting logical units to device units:
x scale factor = x viewport extent / x window extent
y scale factor = y viewport extent / y window extent
device x = logical x × x scale factor + x origin offset
device y = logical y × y scale factor + y origin offset
Suppose the window is 448 pixels wide (rectClient.right). The right edge of the ellipse's client rectangle is 500 logical
units from the origin. The x scale factor is 448/1000, and the x origin offset is 448/2 device units. If you use the formulas
shown on the previous page, the right edge of the ellipse's client rectangle comes out to 448 device units, the right
edge of the window. The x scale factor is expressed as a ratio (viewport extent/window extent) because Windows
device coordinates are integers, not floating-point values. The extent values are meaningless by themselves.
If you substitute MM_ISOTROPIC for MM_ANISOTROPIC in the preceding example, the "ellipse" is always a circle,
as shown in Figure 4-4. It expands to fit the smallest dimension of the window rectangle.

Figure 4-4. A centered ellipse drawn in the MM_ISOTROPIC mapping mode.


22.4 Coordinate Conversion
Once you set the mapping mode (plus the origin) of a device context, you can use logical coordinate parameters for
most CDC member functions. If you get the mouse cursor coordinates from a Windows mouse message (the point
parameter in OnLButtonDown), for example, you're dealing with device coordinates. Many other MFC functions,
particularly the member functions of class CRect, work correctly only with device coordinates.

The CRect arithmetic functions use the underlying Win32 RECT arithmetic functions, which assume
that right is greater than left and bottom is greater than top. A rectangle (0, 0, 1000, -1000) in
MM_HIMETRIC coordinates, for example, has bottom less than top and cannot be processed by
functions such as CRect::PtInRect unless your program first calls CRect::NormalizeRect, which
changes the rectangle's data members to (0, -1000, 1000, 0).
Furthermore, you're likely to need a third set of coordinates that we will call physical coordinates. Why do you need
another set? Suppose you're using the MM_LOENGLISH mapping mode in which a logical unit is 0.01 inch, but an
inch on the screen represents a foot (12 inches) in the real world. Now suppose the user works in inches and
decimal fractions. A measurement of 26.75 inches translates to 223 logical units, which must be ultimately translated
- 34 -

to device coordinates. You will want to store the physical coordinates as either floating-point numbers or scaled long
integers to avoid rounding-off errors.
For the physical-to-logical translation you're on your own, but the Windows GDI takes care of the logical-to-device
translation for you. The CDC functions LPtoDP and DPtoLP translate between the two systems, assuming the device
context mapping mode and associated parameters have already been set. Your job is to decide when to use each
system. Here are a few rules of thumb:
Assume that the CDC member functions take logical coordinate parameters.
Assume that the CWnd member functions take device coordinate parameters.
Do all hit-test operations in device coordinates. Define regions in device coordinates. Functions such as
CRect::PtInRect work best with device coordinates.
Store long-term values in logical or physical coordinates. If you store a point in device coordinates and the user
scrolls through a window, that point is no longer valid.
Suppose you need to know whether the mouse cursor is inside a rectangle when the user presses the left mouse
button. The code is shown here.
// m_rect is CRect data member of the derived view class with MM_LOENGLISH
// logical coordinates

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)


{
CRect rect = m_rect; // rect is a temporary copy of m_rect.
CClientDC dc(this); // This is how we get a device context
// for SetMapMode and LPtoDP
// -- more in next chapter
dc.SetMapMode(MM_LOENGLISH);
dc.LPtoDP(rect); // rect is now in device coordinates
if (rect.PtInRect(point)) {
TRACE("Mouse cursor is inside the rectangle.\n");
}
}
Notice the use of the TRACE macro (covered in Chapter 3).

As you'll soon see, it's better to set the mapping mode in the virtual CView function OnPrepareDC
instead of in the OnDraw function.
22.5 The EX04B Example—Converting to the MM_HIMETRIC Mapping Mode
EX04B is EX04A converted to MM_HIMETRIC coordinates. The EX04B project on the companion CD-ROM uses
new class names and filenames, but the instructions here take you through modifying the EX04A code. Like EX04A,
EX04B performs a hit-test so that the ellipse changes color only when you click inside the bounding rectangle.
Use ClassWizard to override the virtual OnPrepareDC function. ClassWizard can override virtual functions for
selected MFC base classes, including CView. It generates the correct function prototype in the class's header
file and a skeleton function in the CPP file. Select the class name CEx04aView in the Object IDs list, and then
double-click on the OnPrepareDC function in the Messages list. Edit the function as shown here:
void CEx04aView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
pDC->SetMapMode(MM_HIMETRIC);
CView::OnPrepareDC(pDC, pInfo);
}
The application framework calls the virtual OnPrepareDC function just before it calls OnDraw.
Edit the view class constructor. You must change the coordinate values for the ellipse rectangle. That rectangle
is now 4-by-4 centimeters instead of 200-by-200 pixels. Note that the y value must be negative; otherwise, the
ellipse will be drawn on the "virtual screen" right above your monitor! Change the values as shown here:
CEx04aView::CEx04aView() : m_rectEllipse(0, 0, 4000, -4000)
{
m_nColor = GRAY_BRUSH;
}
Edit the OnLButtonDown function. This function must now convert the ellipse rectangle to device coordinates in
order to do the hit-test. Change the function as shown in the following code:
- 35 -

void CEx04aView::OnLButtonDown(UINT nFlags, CPoint point)


{
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectDevice = m_rectEllipse;
dc.LPtoDP(rectDevice);
if (rectDevice.PtInRect(point)) {
if (m_nColor == GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect(rectDevice);
}
}
Build and run the EX04B program. The output should look similar to the output from EX04A, except that the
ellipse size will be different. If you try using Print Preview again, the ellipse should appear much larger than it
did in EX04A.
23. A Scrolling View Window
As the lack of scroll bars in EX04A and EX04B indicates, the MFC CView class, the base class of CEx04bView,
doesn't directly support scrolling. Another MFC library class, CScrollView, does support scrolling. CScrollView is
derived from CView. We'll create a new program, EX04C, that uses CScrollView in place of CView. All the coordinate
conversion code you added in EX04B sets you up for scrolling.
The CScrollView class supports scrolling from the scroll bars but not from the keyboard. It's easy enough to add
keyboard scrolling, so we'll do it.
23.1 A Window Is Larger than What You See
If you use the mouse to shrink the size of an ordinary window, the contents of the window remain anchored at the top
left of the window, and items at the bottom and/or on the right of the window disappear. When you expand the
window, the items reappear. You can correctly conclude that a window is larger than the viewport that you see on the
screen. The viewport doesn't have to be anchored at the top left of the window area, however. Through the use of
the CWnd functions ScrollWindow and SetWindowOrg, the CScrollView class allows you to move the viewport
anywhere within the window, including areas above and to the left of the origin.
23.2 Scroll Bars
Microsoft Windows makes it easy to display scroll bars at the edges of a window, but Windows by itself doesn't make
any attempt to connect those scroll bars to their window. That's where the CScrollView class fits in. CScrollView
member functions process the WM_HSCROLL and WM_VSCROLL messages sent by the scroll bars to the view.
Those functions move the viewport within the window and do all the necessary housekeeping.
23.3 Scrolling Alternatives
The CScrollView class supports a particular kind of scrolling that involves one big window and a small viewport. Each
item is assigned a unique position in this big window. If, for example, you have 10,000 address lines to display,
instead of having a window 10,000 lines long, you probably want a smaller window with scrolling logic that selects
only as many lines as the screen can display. In that case, you should write your own scrolling view class derived
from CView.

Microsoft Windows NT uses 32-bit numbers for logical coordinates, so your logical coordinate space
is almost unlimited. Microsoft Windows 95, however, still has some 16-bit components, so it uses 16-
bit numbers for logical coordinates, limiting values to the range -32,768 to 32,767. Scroll bars send
messages with 16-bit values in both operating systems. With these facts in mind, you probably want
to write code to the lowest common denominator, which is Windows 95.
23.4 The OnInitialUpdate Function
You'll be seeing more of the OnInitialUpdate function when you study the document-view architecture, starting in
Chapter 16. The virtual OnInitial-Update function is important here because it is the first function called by the
framework after your view window is fully created. The framework calls OnInitialUpdate before it calls OnDraw for the
first time, so OnInitialUpdate is the natural place for setting the logical size and mapping mode for a scrolling view.
You set these parameters with a call to the CScrollView::SetScrollSizes function.
23.5 Accepting Keyboard Input
- 36 -

Keyboard input is really a two-step process. Windows sends WM_KEYDOWN and WM_KEYUP messages, with
virtual key codes, to a window, but before they get to the window they are translated. If an ANSI character is typed
(resulting in a WM_KEYDOWN message), the translation function checks the keyboard shift status and then sends a
WM_CHAR message with the proper code, either uppercase or lowercase. Cursor keys and function keys don't have
codes, so there's no translation to do. The window gets only the WM_KEYDOWN and WM_KEYUP messages.
You can use ClassWizard to map all these messages to your view. If you're expecting characters, map WM_CHAR;
if you're expecting other keystrokes, map WM_KEYDOWN. The MFC library neatly supplies the character code or
virtual key code as a handler function parameter.
23.6 The EX04C Example—Scrolling
The goal of EX04C is to make a logical window 20 centimeters wide by 30 centimeters high. The program draws the
same ellipse that it drew in the EX04B project. You could edit the EX04B source files to convert the CView base
class to a CScrollView base class, but it's easier to start over with AppWizard. AppWizard generates the
OnInitialUpdate override function for you. Here are the steps:
Run AppWizard to create EX04C.Use AppWizard to generate a program named EX04C in the \vcpp32\ex04c
subdirectory. In AppWizard Step 6, set the CEx04cView base class to CScrollView, as shown here.

Add the m_rectEllipse and m_nColor data members in ex04cView.h. Insert the following code by right-clicking
the CEx04cView class in the Workspace window or by typing inside the CEx04cView class declaration:
private:
CRect m_rectEllipse;
int m_nColor;
These are the same data members that were added in the EX04A and EX04B projects.
Modify the AppWizard-generated OnInitialUpdate function. Edit OnInitialUpdate in ex04cView.cpp as shown
here:
void CEx04cView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(20000, 30000); // 20 by 30 cm
CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2);
CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50);
SetScrollSizes(MM_HIMETRIC, sizeTotal, sizePage, sizeLine);
}
Use ClassWizard to add a message handler for the WM_KEYDOWN message. ClassWizard generates the
member function OnKeyDown along with the necessary message map entries and prototypes. Edit the code as
follows:
void CEx04cView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
- 37 -

switch (nChar) {
case VK_HOME:
OnVScroll(SB_TOP, 0, NULL);
OnHScroll(SB_LEFT, 0, NULL);
break;
case VK_END:
OnVScroll(SB_BOTTOM, 0, NULL);
OnHScroll(SB_RIGHT, 0, NULL);
break;
case VK_UP:
OnVScroll(SB_LINEUP, 0, NULL);
break;
case VK_DOWN:
OnVScroll(SB_LINEDOWN, 0, NULL);
break;
case VK_PRIOR:
OnVScroll(SB_PAGEUP, 0, NULL);
break;
case VK_NEXT:
OnVScroll(SB_PAGEDOWN, 0, NULL);
break;
case VK_LEFT:
OnHScroll(SB_LINELEFT, 0, NULL);
break;
case VK_RIGHT:
OnHScroll(SB_LINERIGHT, 0, NULL);
break;
default:
break;
}
}
Edit the constructor and the OnDraw function. Change the AppWizard-generated constructor and the OnDraw
function in ex04cView.cpp as follows:
CEx04cView::CEx04cView() : m_rectEllipse(0, 0, 4000, -4000)
{
m_nColor = GRAY_BRUSH;
}
.
.
.
void CEx04cView::OnDraw(CDC* pDC)
{
pDC->SelectStockObject(
m_nColor);
pDC->Ellipse(m_rectEllipse);
}
These functions are identical to those used in the EX04A and EX04B projects.
Map the WM_LBUTTONDOWN message and edit the handler. Make the following changes to the
ClassWizard-generated code:
void CEx04cView::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectDevice = m_rectEllipse;
dc.LPtoDP(rectDevice);
if (rectDevice.PtInRect(point)) {
- 38 -

if (m_nColor == GRAY_BRUSH) {
m_nColor = WHITE_BRUSH;
}
else {
m_nColor = GRAY_BRUSH;
}
InvalidateRect(rectDevice);
}
}
This function is identical to the OnLButtonDown handler in the EX04B project. It calls OnPrepareDC as before,
but there is something different. The CEx04bView class doesn't have an overridden OnPrepareDC function, so
the call goes to CScrollView::OnPrepareDC. That function sets the mapping mode based on the first parameter
to SetScrollSizes, and it sets the window origin based on the current scroll position. Even if your scroll view
used the MM_TEXT mapping mode, you'd still need the coordinate conversion logic to adjust for the origin
offset.
Build and run the EX04C program. Check to be sure the mouse hit logic is working even if the circle is scrolled
partially out of the window. Also check the keyboard logic. The output should look like this.

24. Other Windows Messages


The MFC library directly supports hundreds of Windows message-handling functions. In addition, you can define
your own messages. You will see plenty of message-handling examples in later chapters, including handlers for
menu items, child window controls, and so forth. In the meantime, five special Windows messages deserve special
attention: WM_CREATE, WM_CLOSE, WM_QUERYENDSESSION, WM_DESTROY, and WM_NCDESTROY.
24.1 The WM_CREATE Message
This is the first message that Windows sends to a view. It is sent when the window's Create function is called by the
framework, so the window creation is not finished and the window is not visible. Therefore, your OnCreate handler
cannot call Windows functions that depend on the window being completely alive. You can call such functions in an
overridden OnInitialUpdate function, but you must be aware that in an SDI application OnInitialUpdate can be called
more than once in a view's lifetime.
24.2 The WM_CLOSE Message
Windows sends the WM_CLOSE message when the user closes a window from the system menu and when a
parent window is closed. If you implement the OnClose message map function in your derived view class, you can
control the closing process. If, for example, you need to prompt the user to save changes to a file, you do it in
OnClose. Only when you have determined that it is safe to close the window do you call the base class OnClose
function, which continues the close process. The view object and the corresponding window are both still active.
- 39 -

When you're using the full application framework, you probably won't use the WM_CLOSE message
handler. You can override the CDocument::SaveModified virtual function instead, as part of the
application framework's highly structured program exit procedure.
24.3 The WM_QUERYENDSESSION Message
Windows sends the WM_QUERYENDSESSION message to all running applications when the user exits Windows.
The OnQueryEndSession message map function handles it. If you write a handler for WM_CLOSE, write one for
WM_QUERYENDSESSION too.
24.4 The WM_DESTROY Message
Windows sends this message after the WM_CLOSE message, and the OnDestroy message map function handles it.
When your program receives this message, it should assume that the view window is no longer visible on the screen
but that it is still active and its child windows are still active. Use this message handler to do cleanup that depends on
the existence of the underlying window. Be sure to call the base class OnDestroy function. You cannot "abort" the
window destruction process in your view's OnDestroy function. OnClose is the place to do that.
24.5 The WM_NCDESTROY Message
This is the last message that Windows sends when the window is being destroyed. All child windows have already
been destroyed. You can do final processing in OnNcDestroy that doesn't depend on a window being active. Be sure
to call the base class OnNcDestroy function.

Do not try to destroy a dynamically allocated window object in OnNcDestroy. That job is reserved for
a special CWnd virtual function, PostNcDestroy, that the base class OnNcDestroy calls. MFC
Technical Note #17 in the online documentation gives hints on when it's appropriate to destroy a
window object.
Chapter Five
25. The Graphics Device Interface, Colors, and Fonts
You've already seen some elements of the Graphics Device Interface (GDI). Anytime your program draws to the
display or the printer, it must use the GDI functions. The GDI provides functions for drawing points, lines, rectangles,
polygons, ellipses, bitmaps, and text. You can draw circles and squares intuitively once you study the available
functions, but text programming is more difficult.This chapter gives you the information you need to start using the
GDI effectively in the Microsoft Visual C++ environment. You'll learn how to use fonts on both the display and the
printer. You must wait until Chapter 19, however, for details on how the framework controls the printer.
26. The Device Context Classes
In Chapter 3 and Chapter 4, the view class's OnDraw member function was passed a pointer to a device context
object. OnDraw selected a brush and then drew an ellipse. The Microsoft Windows device context is the key GDI
element that represents a physical device. Each C++ device context object has an associated Windows device
context, identified by a 32-bit handle of type HDC.
Microsoft Foundation Class (MFC) Library version 6.0 provides a number of device context classes. The base class
CDC has all the member functions (including some virtual functions) that you'll need for drawing. Except for the
oddball CMetaFileDC class, derived classes are distinct only in their constructors and destructors. If you (or the
application framework) construct an object of a derived device context class, you can pass a CDC pointer to a
function such as OnDraw. For the display, the usual derived classes are CClientDC and CWindowDC. For other
devices, such as printers or memory buffers, you construct objects of the base class CDC.
The "virtualness" of the CDC class is an important feature of the application framework. In Chapter 19, you'll see how
easy it is to write code that works with both the printer and the display. A statement in OnDraw such as
pDC->TextOut(0, 0, "Hello");
sends text to the display, the printer, or the Print Preview window, depending on the class of the object referenced by
the CView::OnDraw function's pDC parameter.
For display and printer device context objects, the application framework attaches the handle to the object. For other
device contexts, such as the memory device context that you'll see in Chapter 11, you must call a member function
after construction in order to attach the handle.
26.1 The Display Context Classes CClientDC and CWindowDC
Recall that a window's client area excludes the border, the caption bar, and the menu bar. If you create a CClientDC
object, you have a device context that is mapped only to this client area—you can't draw outside it. The point (0, 0)
usually refers to the upper-left corner of the client area. As you'll see later, an MFC CView object corresponds to a
child window that is contained inside a separate frame window, often along with a toolbar, a status bar, and scroll
bars. The client area of the view, then, does not include these other windows. If the window contains a docked
toolbar along the top, for example, (0, 0) refers to the point immediately under the left edge of the toolbar.
If you construct an object of class CWindowDC, the point (0, 0) is at the upper-left corner of the nonclient area of the
window. With this whole-window device context, you can draw in the window's border, in the caption area, and so
- 40 -

forth. Don't forget that the view window doesn't have a nonclient area, so CWindowDC is more applicable to frame
windows than it is to view windows.
26.2 Constructing and Destroying CDC Objects
After you construct a CDC object, it is important to destroy it promptly when you're done with it. Microsoft Windows
limits the number of available device contexts, and if you fail to release a Windows device context object, a small
amount of memory is lost until your program exits. Most frequently, you'll construct a device context object inside a
message handler function such as OnLButtonDown. The easiest way to ensure that the device context object is
destroyed (and that the underlying Windows device context is released) is to construct the object on the stack in the
following way:
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rect;

CClientDC dc(this); // constructs dc on the stack


dc.GetClipBox(rect); // retrieves the clipping rectangle
} // dc automatically released
Notice that the CClientDC constructor takes a window pointer as a parameter. The destructor for the CClientDC
object is called when the function returns. You can also get a device context pointer by using the CWnd::GetDC
member function, as shown in the following code. You must be careful here to call the ReleaseDC function to release
the device context.
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rect;

CDC* pDC = GetDC(); // a pointer to an internal dc


pDC->GetClipBox(rect); // retrieves the clipping rectangle
ReleaseDC(pDC); // Don't forget this
}

You must not destroy the CDC object passed by the pointer to OnDraw. The application framework
handles the destruction for you.
26.3 The State of the Device Context
You already know that a device context is required for drawing. When you use a CDC object to draw an ellipse, for
example, what you see on the screen (or on the printer's hard copy) depends on the current "state" of the device
context. This state includes the following:
Attached GDI drawing objects such as pens, brushes, and fonts
The mapping mode that determines the scale of items when they are drawn (You've already experimented with
the mapping mode in Chapter 4.)
Various details such as text alignment parameters and polygon filling mode
You have already seen, for example, that choosing a gray brush prior to drawing an ellipse results in the ellipse
having a gray interior. When you create a device context object, it has certain default characteristics, such as a black
pen for shape boundaries. All other state characteristics are assigned through CDC class member functions. GDI
objects are selected into the device context by means of the overloaded SelectObject functions. A device context
can, for example, have one pen, one brush, or one font selected at any given time.
26.4 The CPaintDC Class
You'll need the CPaintDC class only if you override your view's OnPaint function. The default OnPaint calls OnDraw
with a properly set up device context, but sometimes you'll need display-specific drawing code. The CPaintDC class
is special because its constructor and destructor do housekeeping unique to drawing to the display. Once you have a
CDC pointer, however, you can use it as you would any other device context pointer.
Here's a sample OnPaint function that creates a CPaintDC object:
void CMyView::OnPaint()
{
CPaintDC dc(this);
OnPrepareDC(&dc); // explained later
dc.TextOut(0, 0, "for the display, not the printer");
OnDraw(&dc); // stuff that's common to display and printer
- 41 -

}
For Win32 Programmers
The CPaintDC constructor calls BeginPaint for you, and the destructor calls EndPaint. If you
construct your device context on the stack, the EndPaint call is completely automatic.
27. GDI Objects
A Windows GDI object type is represented by an MFC library class. CGdiObject is the abstract base class for the
GDI object classes. A Windows GDI object is represented by a C++ object of a class derived from CGdiObject.
Here's a list of the GDI derived classes:
CBitmap—A bitmap is an array of bits in which one or more bits correspond to each display pixel. You can use
bitmaps to represent images, and you can use them to create brushes.
CBrush—A brush defines a bitmapped pattern of pixels that is used to fill areas with color.
CFont—A font is a complete collection of characters of a particular typeface and a particular size. Fonts are
generally stored on disk as resources, and some are device-specific.
CPalette—A palette is a color mapping interface that allows an application to take full advantage of the color
capability of an output device without interfering with other applications.
CPen—A pen is a tool for drawing lines and shape borders. You can specify a pen's color and thickness and
whether it draws solid, dotted, or dashed lines.
CRgn—A region is an area whose shape is a polygon, an ellipse, or a combination of polygons and ellipses. You
can use regions for filling, clipping, and mouse hit-testing.
27.1 Constructing and Destroying GDI Objects
You never construct an object of class CGdiObject; instead, you construct objects of the derived classes.
Constructors for some GDI derived classes, such as CPen and CBrush, allow you to specify enough information to
create the object in one step. Others, such as CFont and CRgn, require a second creation step. For these classes,
you construct the C++ object with the default constructor and then you call a create function such as the CreateFont
or CreatePolygonRgn function.
The CGdiObject class has a virtual destructor. The derived class destructors delete the Windows GDI objects that
are attached to the C++ objects. If you construct an object of a class derived from CGdiObject, you must delete it
prior to exiting the program. To delete a GDI object, you must first separate it from the device context. You'll see an
example of this in the next section.

Failure to delete a GDI object was a serious offense with Win16. GDI memory was not released until
the user restarted Windows. With Win32, however, the GDI memory is owned by the process and is
released when your program terminates. Still, an unreleased GDI bitmap object can waste a
significant amount of memory.
27.2 Tracking GDI Objects
OK, so you know that you have to delete your GDI objects and that they must first be disconnected from their device
contexts. How do you disconnect them? A member of the CDC::SelectObject family of functions does the work of
selecting a GDI object into the device context, and in the process it returns a pointer to the previously selected object
(which gets deselected in the process). Trouble is, you can't deselect the old object without selecting a new object.
One easy way to track the objects is to "save" the original GDI object when you select your own GDI object and
"restore" the original object when you're finished. Then you'll be ready to delete your own GDI object. Here's an
example:

void CMyView::OnDraw(CDC* pDC)


{
CPen newPen(PS_DASHDOTDOT, 2, (COLORREF) 0); // black pen,
// 2 pixels wide
CPen* pOldPen = pDC->SelectObject(&newPen);

pDC->MoveTo(10, 10);
pDC->Lineto(110, 10);
pDC->SelectObject(pOldPen); // newPen is deselected
} // newPen automatically destroyed on exit
When a device context object is destroyed, all its GDI objects are deselected. Thus, if you know that a device context
will be destroyed before its selected GDI objects are destroyed, you don't have to deselect the objects. If, for
example, you declare a pen as a view class data member (and you initialize it when you initialize the view), you don't
- 42 -

have to deselect the pen inside OnDraw because the device context, controlled by the view base class's OnPaint
handler, will be destroyed first.
27.3 Stock GDI Objects
Windows contains a number of stock GDI objects that you can use. Because these objects are part of Windows, you
don't have to worry about deleting them. (Windows ignores requests to delete stock objects.) The MFC library
function CDC::SelectStockObject selects a stock object into the device context and returns a pointer to the previously
selected object, which it deselects. Stock objects are handy when you want to deselect your own nonstock GDI
object prior to its destruction. You can use a stock object as an alternative to the "old" object you used in the
previous example, as shown here:
void CMyView::OnDraw(CDC* pDC)
{
CPen newPen(PS_DASHDOTDOT, 2, (COLORREF) 0); // black pen,
// 2 pixels wide

pDC->SelectObject(&newPen);
pDC->MoveTo(10, 10);
pDC->Lineto(110, 10);
pDC->SelectStockObject(BLACK_PEN); // newPen is deselected
} // newPen destroyed on exit
The Microsoft Foundation Class Reference lists, under CDC::SelectStockObject, the stock objects available for pens,
brushes, fonts, and palettes.
27.4 The Lifetime of a GDI Selection
For the display device context, you get a "fresh" device context at the beginning of each message handler function.
No GDI selections (or mapping modes or other device context settings) persist after your function exits. You must,
therefore, set up your device context from scratch each time. The CView class virtual member function
OnPrepareDC is useful for setting the mapping mode, but you must manage your own GDI objects.
For other device contexts, such as those for printers and memory buffers, your assignments can last longer. For
these long-life device contexts, things get a little more complicated. The complexity results from the temporary nature
of GDI C++ object pointers returned by the SelectObject function. (The temporary "object" will be destroyed by the
application framework during the idle loop processing of the application, sometime after the handler function returns
the call. See MFC Technical Note #3 in the online documentation.) You can't simply store the pointer in a class data
member; instead, you must convert it to a Windows handle (the only permanent GDI identifier) with the GetSafeHdc
member function. Here's an example:
// m_pPrintFont points to a CFont object created in CMyView's constructor
// m_hOldFont is a CMyView data member of type HFONT, initialized to 0

void CMyView::SwitchToCourier(CDC* pDC)


{
m_pPrintFont->CreateFont(30, 10, 0, 0, 400, FALSE, FALSE,
0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_MODERN,
"Courier New"); // TrueType
CFont* pOldFont = pDC->SelectObject(m_pPrintFont);

// m_hOldFont is the CGdiObject public data member that stores


// the handle
m_hOldFont = (HFONT) pOldFont->GetSafeHandle();
}

void CMyView:SwitchToOriginalFont(CDC* pDC)


{
// FromHandle is a static member function that returns an
// object pointer
if (m_hOldFont) {
pDC->SelectObject(CFont::FromHandle(m_hOldFont));
}
- 43 -

// m_pPrintFont is deleted in the CMyView destructor

Be careful when you delete an object whose pointer is returned by SelectObject. If you've allocated
the object yourself, you can delete it. If the pointer is temporary, as it will be for the object initially
selected into the device context, you won't be able to delete the C++ object.
28. Windows Color Mapping
The Windows GDI provides a hardware-independent color interface. Your program supplies an "absolute" color
code, and the GDI maps that code to a suitable color or color combination on your computer's video display. Most
programmers of applications for Windows try to optimize their applications' color display for a few common video
card categories.
28.1 Standard Video Graphics Array Video Cards
A standard Video Graphics Array (VGA) video card uses 18-bit color registers and thus has a palette of 262,144
colors. Because of video memory constraints, however, the standard VGA board accommodates 4-bit color codes,
which means it can display only 16 colors at a time. Because Windows needs fixed colors for captions, borders,
scroll bars, and so forth, your programs can use only 16 "standard" pure colors. You cannot conveniently access the
other colors that the board can display.
Each Windows color is represented by a combination of 8-bit "red," "green," and "blue" values. The 16 standard VGA
"pure" (nondithered) colors are shown in the table below.
Color-oriented GDI functions accept 32-bit COLORREF parameters that contain 8-bit color codes each for red,
green, and blue. The Windows RGB macro converts 8-bit red, green, and blue values to a COLORREF parameter.
The following statement, when executed on a system with a standard VGA board, constructs a brush with a dithered
color (one that consists of a pattern of pure-color pixels):
CBrush brush(RGB(128, 128, 192));
Red Green Blue Color

0 0 0 Black

0 0 255 Blue

0 255 0 Green

0 255 255 Cyan

255 0 0 Red

255 0 255 Magenta


255 255 0 Yellow

255 255 255 White

0 0 128 Dark blue

0 128 0 Dark green

0 128 128 Dark cyan

128 0 0 Dark red

128 0 128 Dark magenta

128 128 0 Dark yellow

128 128 128 Dark gray

192 192 192 Light gray


The following statement (in your view's OnDraw function) sets the text background to red:
pDC->SetBkColor(RGB(255, 0, 0));
The CDC functions SetBkColor and SetTextColor don't display dithered colors as the brush-oriented drawing
functions do. If the dithered color pattern is too complex, the closest matching pure color is displayed.
- 44 -

28.2 256-Color Video Cards


Most video cards can accommodate 8-bit color codes at all resolutions, which means they can display 256 colors
simultaneously. This 256-color mode is now considered to be the "lowest common denominator" for color
programming.
If Windows is configured for a 256-color display card, your programs are limited to 20 standard pure colors unless
you activate the Windows color palette system as supported by the MFC library CPalette class and the Windows
API, in which case you can choose your 256 colors from a total of more than 16.7 million. Windows color palette
programming is discussed in Chapter 11. In this chapter, we'll assume that the Windows default color mapping is in
effect.
With an SVGA 256-color display driver installed, you get the 16 VGA colors listed in the previous table plus 4 more,
for a total of 20. The following table lists the 4 additional colors.
Red Green Blue Color

192 220 192 Money green

166 202 240 Sky blue

255 251 240 Cream

160 160 164 Medium gray


The RGB macro works much the same as it does with the standard VGA. If you specify one of the 20 standard colors
for a brush, you get a pure color; otherwise, you get a dithered color. If you use the PALETTERGB macro instead,
you don't get dithered colors; you get the closest matching standard pure color as defined by the current palette.
28.3 16-Bit-Color Video Cards
Most modern video cards support a resolution of 1024-by-768 pixels, and 1 MB of video memory can support 8-bit
color at this resolution. If a video card has 2 MB of memory, it can support 16-bit color, with 5 bits each for red,
green, and blue. This means that it can display 32,768 colors simultaneously. That sounds like a lot, but there are
only 32 shades each of pure red, green, and blue. Often, a picture will look better in 8-bit-color mode with an
appropriate palette selected. A forest scene, for example, can use up to 236 shades of green. Palettes are not
supported in 16-bit-color mode.
28.4 24-Bit-Color Video Cards
High-end cards (which are becoming more widely used) support 24-bit color. This 24-bit capability enables the
display of more than 16.7 million pure colors. If you're using a 24-bit card, you have direct access to all the colors.
The RGB macro allows you to specify the exact colors you want. You'll need 2.5 MB of video memory, though, if you
want 24-bit color at 1024-by-768-pixel resolution.
29. Fonts
Old-fashioned character-mode applications could display only the boring system font on the screen. Windows
provides multiple device-independent fonts in variable sizes. The effective use of these Windows fonts can
significantly energize an application with minimum programming effort. TrueType fonts, first introduced with Windows
version 3.1, are even more effective and are easier to program than the previous device-dependent fonts. You'll see
several example programs that use various fonts later in this chapter.
29.1 Fonts Are GDI Objects
Fonts are an integral part of the Windows GDI. This means that fonts behave the same way other GDI objects do.
They can be scaled and clipped, and they can be selected into a device context as a pen or a brush can be selected.
All GDI rules about deselection and deletion apply to fonts.
29.2 Choosing a Font
Choosing a Windows font used to be like going to a fruit stand and asking for "a piece of reddish-yellow fruit, with a
stone inside, that weighs about 4 ounces." You might have gotten a peach or a plum or even a nectarine, and you
could be sure that it wouldn't have weighed exactly 4 ounces. Once you took possession of the fruit, you could weigh
it and check the fruit type. Now, with TrueType, you can specify the fruit type, but you still can't specify the exact
weight.
Today you can choose between two font types—device-independent TrueType fonts and device-dependent fonts
such as the Windows display System font and the LaserJet LinePrinter font—or you can specify a font category and
size and let Windows select the font for you. If you let Windows select the font, it will choose a TrueType font if
possible. The MFC library provides a font selection dialog box tied to the currently selected printer, so there's little
need for printer font guesswork. You let the user select the exact font and size for the printer, and then you
approximate the display the best you can.
29.3 Printing with Fonts
- 45 -

For text-intensive applications, you'll probably want to specify printer font sizes in points (1 point = 1/72 inch). Why?
Most, if not all, built-in printer fonts are defined in terms of points. The LaserJet LinePrinter font, for example, comes
in one size, 8.5 point. You can specify TrueType fonts in any point size. If you work in points, you need a mapping
mode that easily accommodates points. That's what MM_TWIPS is for. An 8.5-point font is 8.5 × 20, or 170, twips,
and that's the character height you'll want to specify.
29.4 Displaying Fonts
If you're not worried about the display matching the printed output, you have a lot of flexibility. You can choose any of
the scalable Windows TrueType fonts, or you can choose the fixed-size system fonts (stock objects). With the
TrueType fonts, it doesn't much matter what mapping mode you use; simply choose a font height and go for it. No
need to worry about points.
Matching printer fonts to make printed output match the screen presents some problems, but TrueType makes it
easier than it used to be. Even if you're printing with TrueType fonts, however, you'll never quite get the display to
match the printer output. Why? Characters are ultimately displayed in pixels (or dots), and the width of a string of
characters is equal to the sum of the pixel widths of its characters, possibly adjusted for kerning. The pixel width of
the characters depends on the font, the mapping mode, and the resolution of the output device. Only if both the
printer and the display were set to MM_TEXT mode (1 pixel or dot = 1 logical unit) would you get an exact
correspondence. If you're using the CDC::GetTextExtent function to calculate line breaks, the screen breakpoint will
occasionally be different from the printer breakpoint.

In the MFC Print Preview mode, which we'll examine closely in Chapter 19, line breaks occur exactly
as they do on the printer, but the print quality in the preview window suffers in the process.
If you're matching a printer-specific font on the screen, TrueType again makes the job easier. Windows substitutes
the closest matching TrueType font. For the 8.5-point LinePrinter font, Windows comes pretty close with its Courier
New font.
29.5 Logical Inches and Physical Inches on the Display
The CDC member function GetDeviceCaps returns various display measurements that are important to your
graphics programming. The six described below provide information about the display size. The values listed are for
a typical display card configured for a resolution of 640-by-480 pixels with Microsoft Windows NT 4.0.
Index Description Value

HORZSIZE Physical width in millimeters 320

VERTSIZE Physical height in millimeters 240

HORZRES Width in pixels 640

VERTRES Height in raster lines 480


LOGPIXELSX Horizontal dots per logical inch 96

LOGPIXELSY Vertical dots per logical inch 96


The indexes HORZSIZE and VERTSIZE represent the physical dimensions of your display. (These indexes might
not be true since Windows doesn't know what size display you have connected to your video adapter.) You can also
calculate a display size by multiplying HORZRES and VERTRES by LOGPIXELSX and LOGPIXELSY, respectively.
The size calculated this way is known as the logical size of the display. Using the values above and the fact that
there are 25.4 millimeters per inch, we can quickly calculate the two display sizes for a 640-by-480 pixel display
under Windows NT 4.0. The physical display size is 12.60-by-9.45 inches, and the logical size is 6.67-by-5.00
inches. So the physical size and the logical size need not be the same.
For Windows NT 4.0, it turns out that HORZSIZE and VERTSIZE are independent of the display resolution, and
LOGPIXELSX and LOGPIXELSY are always 96. So the logical size changes for different display resolutions, but the
physical size does not. For Windows 95, the logical size and the physical size are equal, so both change with the
display resolution. (At a resolution of 640-by-480 pixels with Windows 95, HORZSIZE is 169 and VERTSIZE is 127.)
Whenever you use a fixed mapping mode such as MM_HIMETRIC or MM_TWIPS, the display driver uses the
physical display size to do the mapping.
So, for Windows NT, text is smaller on a small monitor; but that's not what you want. Instead, you want your font
sizes to correspond to the logical display size, not the physical size.
You can invent a special mapping mode, called logical twips, for which one logical unit is equal to 1/1440 logical inch.
This mapping mode is independent of the operating system and display resolution and is used by programs such as
Microsoft Word. Here is the code that sets the mapping mode to logical twips:
pDC->SetMapMode(MM_ANISOTROPIC);
- 46 -

pDC->SetWindowExt(1440, 1440);
pDC->SetViewportExt(pDC->GetDeviceCaps(LOGPIXELSX),
-pDC->GetDeviceCaps(LOGPIXELSY));

From the Windows Control Panel, you can adjust both the display font size and the display
resolution. If you change the display font size from the default 100 percent to 200 percent,
HORZSIZE becomes 160, VERTSIZE becomes 120, and the dots-per-inch value becomes 192. In
that case, the logical size is divided by 2, and all text drawn with the logical twips mapping mode is
doubled in size.
29.6 Computing Character Height
Five font height measurement parameters are available through the CDC function GetTextMetrics, but only three are
significant. Figure 5-1 shows the important font measurements. The tmHeight parameter represents the full height of
the font, including descenders (for the characters g, j, p, q, and y) and any diacritics that appear over capital letters.
The tmExternalLeading parameter is the distance between the top of the diacritic and the bottom of the descender
from the line above. The sum of tmHeight and tmExternalLeading is the total character height. The value of
tmExternalLeading can be 0.

Figure 5-1. Font height measurements.


You would think that tmHeight would represent the font size in points. Wrong! Another GetTextMetrics parameter,
tmInternalLeading, comes into play. The point size corresponds to the difference between tmHeight and
tmInternalLeading. With the MM_TWIPS mapping mode in effect, a selected 12-point font might have a tmHeight
value of 295 logical units and a tmInter-nalLeading value of 55. The font's net height of 240 corresponds to the point
size of 12.
30. The EX05A Example
This example sets up a view window with the logical twips mapping mode. A text string is displayed in 10 point sizes
with the Arial TrueType font. Here are the steps for building the application:
Run AppWizard to generate the EX05A project. Start by choosing New from the File menu, and then select
MFC AppWizard (exe) on the Project tab. Select Single Document and deselect Printing And Print Preview;
accept all the other default settings. The options and the default class names are shown in the following
illustration.
- 47 -

Use ClassWizard to override the OnPrepareDC function in the CEx05aView class. Edit the code in
ex05aView.cpp as follows:
void CEx05aView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
pDC->SetMapMode(MM_ANISOTROPIC);
pDC->SetWindowExt(1440, 1440);
pDC->SetViewportExt(pDC->GetDeviceCaps(LOGPIXELSX),
-pDC->GetDeviceCaps(LOGPIXELSY));
}
Add a private ShowFont helper function to the view class. Add the prototype shown below in
ex05aView.h:
private:
void ShowFont(CDC* pDC, int& nPos, int nPoints);
Then add the function itself in ex05aView.cpp:
void CEx05aView::ShowFont(CDC* pDC, int& nPos, int nPoints)
{
TEXTMETRIC tm;
CFont fontText;
CString strText;
CSize sizeText;

fontText.CreateFont(-nPoints * 20, 0, 0, 0, 400, FALSE, FALSE, 0,


ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_SWISS, "Arial");
CFont* pOldFont = (CFont*) pDC->SelectObject(&fontText);
pDC->GetTextMetrics(&tm);
TRACE("points = %d, tmHeight = %d, tmInternalLeading = %d,"
" tmExternalLeading = %d\n", nPoints, tm.tmHeight,
tm.tmInternalLeading, tm.tmExternalLeading);
- 48 -

strText.Format("This is %d-point Arial", nPoints);


sizeText = pDC->GetTextExtent(strText);
TRACE("string width = %d, string height = %d\n", sizeText.cx,
sizeText.cy);
pDC->TextOut(0, nPos, strText);
pDC->SelectObject(pOldFont);
nPos -= tm.tmHeight + tm.tmExternalLeading;
}
Edit the OnDraw function in ex05aView.cpp. AppWizard always generates a skeleton OnDraw function for
your view class. Find the function, and replace the code with the following:
void CEx05aView::OnDraw(CDC* pDC)
{
int nPosition = 0;

for (int i = 6; i <= 24; i += 2) {


ShowFont(pDC, nPosition, i);
}
TRACE("LOGPIXELSX = %d, LOGPIXELSY = %d\n",
pDC->GetDeviceCaps(LOGPIXELSX),
pDC->GetDeviceCaps(LOGPIXELSY));
TRACE("HORZSIZE = %d, VERTSIZE = %d\n",
pDC->GetDeviceCaps(HORZSIZE),
pDC->GetDeviceCaps(VERTSIZE));
TRACE("HORZRES = %d, VERTRES = %d\n",
pDC->GetDeviceCaps(HORZRES),
pDC->GetDeviceCaps(VERTRES));
}
Build and run the EX05A program. You must run the program from the debugger if you want to see the
output from the TRACE statements. You can choose Go from the Start Debug submenu of the Build menu
in Visual C++, or click the following button on the Build toolbar.

The resulting output (assuming the use of a standard VGA card) looks like the screen shown here.

Notice that the output string sizes don't quite correspond to the point sizes. This discrepancy results from
the font engine's conversion of logical units to pixels. The program's trace output, partially shown below,
shows the printout of font metrics. (The numbers depend on your display driver and your video driver.)
points = 6, tmHeight = 150, tmInternalLeading = 30, tmExternalLeading = 4
string width = 990, string height = 150
points = 8, tmHeight = 210, tmInternalLeading = 45, tmExternalLeading = 5
- 49 -

string width = 1380, string height = 210


points = 10, tmHeight = 240, tmInternalLeading = 45, tmExternalLeading = 6
string width = 1770, string height = 240
points = 12, tmHeight = 270, tmInternalLeading = 30, tmExternalLeading = 8
string width = 2130, string height = 270
30.1 The EX05A Program Elements
Following is a discussion of the important elements in the EX05A example.
30.1.1 Setting the Mapping Mode in the OnPrepareDC Function
The application framework calls OnPrepareDC prior to calling OnDraw, so the OnPrepareDC function is the
logical place to prepare the device context. If you had other message handlers that needed the correct mapping
mode, those functions would have contained calls to OnPrepareDC.
30.1.2 The ShowFont Private Member Function
ShowFont contains code that is executed 10 times in a loop. With C, you would have made this a global
function, but with C++ it's better to make it a private class member function, sometimes known as a helper
function.
This function creates the font, selects it into the device context, prints a string to the window, and then
deselects the font. If you choose to include debug information in the program, ShowFont also displays useful
font metrics information, including the actual width of the string.
30.1.3 Calling CFont::CreateFont
This call includes lots of parameters, but the important ones are the first two—the font height and width. A width
value of 0 means that the aspect ratio of the selected font will be set to a value specified by the font designer. If
you put a nonzero value here, as you'll see in the next example, you can change the font's aspect ratio.

If you want your font to be a specific point size, the CreateFont font height parameter (the first
parameter) must be negative. If you're using the MM_TWIPS mapping mode for a printer, for
example, a height parameter of -240 ensures a true 12-point font, with tmHeight -
tmInternalLeading = 240. A +240 height parameter gives you a smaller font, with tmHeight =
240.
The last CreateFont parameter specifies the font name, in this case the Arial TrueType font. If you had used
NULL for this parameter, the FF_SWISS specification (which indicates a proportional font without serifs) would
have caused Windows to choose the best matching font, which, depending on the specified size, might have
been the System font or the Arial TrueType font. The font name takes precedence. If you had specified
FF_ROMAN (which indicates a proportional font with serifs) with Arial, for example, you would have gotten
Arial.
31. The EX05B Example
This program is similar to EX05A except that it shows multiple fonts. The mapping mode is MM_ANISOTROPIC, with
the scale dependent on the window size. The characters change size along with the window. This program
effectively shows off some TrueType fonts and contrasts them with the old-style fonts. Here are the steps for building
the application:
Run AppWizard to generate the EX05B project. The options and the default class names are shown here.
- 50 -

Use ClassWizard to override the OnPrepareDC function in the CEx05bView class. Edit the code in
ex05bView.cpp as shown below.
void CEx05bView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
CRect clientRect;

GetClientRect(clientRect);
pDC->SetMapMode(MM_ANISOTROPIC); // +y = down
pDC->SetWindowExt(400, 450);
pDC->SetViewportExt(clientRect.right, clientRect.bottom);
pDC->SetViewportOrg(0, 0);
}
Add a private TraceMetrics helper function to the view class. Add the following prototype in ex05bView.h:
private:
void TraceMetrics(CDC* pDC);
Then add the function itself in ex05bView.cpp:
void CEx05bView::TraceMetrics(CDC* pDC)
{
TEXTMETRIC tm;
char szFaceName[100];

pDC->GetTextMetrics(&tm);
pDC->GetTextFace(99, szFaceName);
TRACE("font = %s, tmHeight = %d, tmInternalLeading = %d,"
" tmExternalLeading = %d\n", szFaceName, tm.tmHeight,
tm.tmInternalLeading, tm.tmExternalLeading);
}
Edit the OnDraw function in ex05bView.cpp. AppWizard always generates a skeleton OnDraw function for your
view class. Find the function, and edit the code as follows:
void CEx05bView::OnDraw(CDC* pDC)
- 51 -

{
CFont fontTest1, fontTest2, fontTest3, fontTest4;

fontTest1.CreateFont(50, 0, 0, 0, 400, FALSE, FALSE, 0,


ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_SWISS, "Arial");
CFont* pOldFont = pDC->SelectObject(&fontTest1);
TraceMetrics(pDC);
pDC->TextOut(0, 0, "This is Arial, default width");

fontTest2.CreateFont(50, 0, 0, 0, 400, FALSE, FALSE, 0,


ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_MODERN, "Courier");
// not TrueType
pDC->SelectObject(&fontTest2);
TraceMetrics(pDC);
pDC->TextOut(0, 100, "This is Courier, default width");

fontTest3.CreateFont(50, 10, 0, 0, 400, FALSE, FALSE, 0,


ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_ROMAN, NULL);
pDC->SelectObject(&fontTest3);
TraceMetrics(pDC);
pDC->TextOut(0, 200, "This is generic Roman, variable width");

fontTest4.CreateFont(50, 0, 0, 0, 400, FALSE, FALSE, 0,


ANSI_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_MODERN, "LinePrinter");
pDC->SelectObject(&fontTest4);
TraceMetrics(pDC);
pDC->TextOut(0, 300, "This is LinePrinter, default width");
pDC->SelectObject(pOldFont);
}
Build and run the EX05B program. Run the program from the debugger to see the TRACE output. The
program's window is shown here.
- 52 -

Resize the window to make it smaller, and watch the font sizes change. Compare this window with the previous
one.

If you continue to downsize the window, notice how the Courier font stops shrinking after a certain size and how
the Roman font width changes.
31.1 The EX05B Program Elements
Following is a discussion of the important elements in the EX05B example.
31.1.1 The OnDraw Member Function
The OnDraw function displays character strings in four fonts, as follows:
fontTest1—The TrueType font Arial with default width selection.
fontTest2—The old-style font Courier with default width selection. Notice how jagged the font appears in larger
sizes.
fontTest3—The generic Roman font for which Windows supplies the TrueType font Times New Roman with
programmed width selection. The width is tied to the horizontal window scale, so the font stretches to fit the
window.
fontTest4—The LinePrinter font is specified, but because this is not a Windows font for the display, the font engine
falls back on the FF_MODERN specification and chooses the TrueType Courier New font.
31.1.2 The TraceMetrics Helper Function
The TraceMetrics helper function calls CDC::GetTextMetrics and CDC::GetTextFace to get the current font's
parameters, which it prints in the Debug window.
32. The EX05C Example—CScrollView Revisited
You saw the CScrollView class in Chapter 4 (in EX04C). The EX05C program allows the user to move an ellipse with
a mouse by "capturing" the mouse, using a scrolling window with the MM_LOENGLISH mapping mode. Keyboard
scrolling is left out, but you can add it by borrowing the OnKeyDown member function from EX04C.
Instead of a stock brush, we'll use a pattern brush for the ellipse—a real GDI object. There's one complication with
pattern brushes: you must reset the origin as the window scrolls; otherwise, strips of the pattern don't line up and the
effect is ugly.
As with the EX04C program, this example involves a view class derived from CScrollView. Here are the steps to
create the application:
Run AppWizard to generate the EX05C project. Be sure to set the view base class to CScrollView. The options
and the default class names are shown here.
- 53 -

Edit the CEx05cView class header in the file ex05cView.h.Add the following lines in the class CEx05cView
declaration:
private:
const CSize m_sizeEllipse; //
logical
CPoint m_pointTopLeft; // logical, top left of ellipse rectangle
CSize m_sizeOffset; // device, from rect top left

// to capture point
BOOL m_bCaptured;
Use ClassWizard to add three message handlers to the CEx05cView class. Add the message handlers as
follows:
Message Member Function

WM_LBUTTONDOWN OnLButtonDown

WM_LBUTTONUP OnLButtonUp

WM_MOUSEMOVE OnMouseMove
Edit the CEx05cView message handler functions. ClassWizard generated the skeletons for the functions listed
in the preceding step. Find the functions in ex05cView.cpp, and code them as follows.
void CEx05cView::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rectEllipse(m_pointTopLeft, m_sizeEllipse); // still logical
CRgn circle;

CClientDC dc(this);
OnPrepareDC(&dc);
dc.LPtoDP(rectEllipse); // Now it's in device coordinates
circle.CreateEllipticRgnIndirect(rectEllipse);
if (circle.PtInRegion(point)) {
- 54 -

// Capturing the mouse ensures subsequent LButtonUp message


SetCapture();
m_bCaptured = TRUE;
CPoint pointTopLeft(m_pointTopLeft);
dc.LPtoDP(&pointTopLeft);
m_sizeOffset = point - pointTopLeft; // device coordinates
// New mouse cursor is active while mouse is captured
::SetCursor(::LoadCursor(NULL, IDC_CROSS));
}
}

void CEx05cView::OnLButtonUp(UINT nFlags, CPoint point)


{
if (m_bCaptured) {
::ReleaseCapture();
m_bCaptured = FALSE;
}
}

void CEx05cView::OnMouseMove(UINT nFlags, CPoint point)


{
if (m_bCaptured) {
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectOld(m_pointTopLeft, m_sizeEllipse);
dc.LPtoDP(rectOld);
InvalidateRect(rectOld, TRUE);
m_pointTopLeft = point - m_sizeOffset;
dc.DPtoLP(&m_pointTopLeft);
CRect rectNew(m_pointTopLeft, m_sizeEllipse);
dc.LPtoDP(rectNew);
InvalidateRect(rectNew, TRUE);
}
}
Edit the CEx05cView constructor, the OnDraw function, and the OnInitialUpdate function. AppWizard generated
these skeleton functions. Find them in ex05cView.cpp, and code them as follows:
CEx05cView::CEx05cView() : m_sizeEllipse(100, -100),
m_pointTopLeft(0, 0),
m_sizeOffset(0, 0)
{
m_bCaptured = FALSE;
}

void CEx05cView::OnDraw(CDC* pDC)


{
CBrush brushHatch(HS_DIAGCROSS, RGB(255, 0, 0));
CPoint point(0, 0); // logical (0, 0)

pDC->LPtoDP(&point); // In device coordinates,


pDC->SetBrushOrg(point); // align the brush with
// the window origin
pDC->SelectObject(&brushHatch);
pDC->Ellipse(CRect(m_pointTopLeft, m_sizeEllipse));
pDC->SelectStockObject(BLACK_BRUSH); // Deselect brushHatch
pDC->Rectangle(CRect(100, -100, 200, -200)); // Test invalid rect
- 55 -

void CEx05cView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();

CSize sizeTotal(800, 1050); // 8-by-10.5 inches


CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2);
CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50);
SetScrollSizes(MM_LOENGLISH, sizeTotal, sizePage, sizeLine);
}
Build and run the EX05C program. The program allows an ellipse to be dragged with the mouse, and it allows
the window to be scrolled through. The program's window should look like the one shown here. As you move
the ellipse, observe the black rectangle. You should be able to see the effects of invalidating the rectangle.

32.1 The EX05C Program Elements


Following is a discussion of the important elements in the EX05C example.
32.1.1 The m_sizeEllipse and m_pointTopLeft Data Members
Rather than store the ellipse's bounding rectangle as a single CRect object, the program separately stores its size
(m_sizeEllipse) and the position of its top left corner (m_pointTopLeft). To move the ellipse, the program merely
recalculates m_pointTopLeft, and any round-off errors in the calculation won't affect the size of the ellipse.
32.1.2 The m_sizeOffset Data Member
When OnMouseMove moves the ellipse, the relative position of the mouse within the ellipse must be the same as it
was when the user first pressed the left mouse button. The m_sizeOffset object stores this original offset of the
mouse from the top left corner of the ellipse rectangle.
32.1.3 The m_bCaptured Data Member
The m_bCaptured Boolean variable is set to TRUE when mouse tracking is in progress.
32.1.4 The SetCapture and ReleaseCapture Functions
SetCapture is the CWnd member function that "captures" the mouse, such that mouse movement messages are
sent to this window even if the mouse cursor is outside the window. An unfortunate side effect of this function is that
the ellipse can be moved outside the window and "lost." A desirable and necessary effect is that all subsequent
mouse messages are sent to the window, including the WM_LBUTTONUP message, which would otherwise be lost.
The Win32 ReleaseCapture function turns off mouse capture.
32.1.5 The SetCursor and LoadCursor Win32 Functions
- 56 -

The MFC library does not "wrap" some Win32 functions. By convention, we use the C++ scope resolution operator
(::) when calling Win32 functions directly. In this case, there is no potential for conflict with a CView member function,
but you can deliberately choose to call a Win32 function in place of a class member function with the same name. In
that case, the :: operator ensures that you call the globally scoped Win32 function.
When the first parameter is NULL, the LoadCursor function creates a cursor resource from the specified predefined
mouse cursor that Windows uses. The SetCursor function activates the specified cursor resource. This cursor
remains active as long as the mouse is captured.
32.1.6 The CScrollView::OnPrepareDC Member Function
The CView class has a virtual OnPrepareDC function that does nothing. The CScrollView class implements the
function for the purpose of setting the view's mapping mode and origin, based on the parameters that you passed to
SetScrollSizes in OnCreate. The application framework calls OnPrepareDC for you prior to calling OnDraw, so you
don't need to worry about it. You must call OnPrepareDC yourself in any other message handler function that uses
the view's device context, such as OnLButtonDown and OnMouseMove.
32.1.7 The OnMouseMove Coordinate Transformation Code
As you can see, this function contains several translation statements. The logic can be summarized by the following
steps:
Construct the previous ellipse rectangle and convert it from logical to device coordinates.
Invalidate the previous rectangle.
Update the top left coordinate of the ellipse rectangle.
Construct the new rectangle and convert it to device coordinates.
Invalidate the new rectangle.
The function calls InvalidateRect twice. Windows "saves up" the two invalid rectangles and computes a new invalid
rectangle that is the union of the two, intersected with the client rectangle.
32.1.8 The OnDraw Function
The SetBrushOrg call is necessary to ensure that all of the ellipse's interior pattern lines up when the user scrolls
through the view. The brush is aligned with a reference point, which is at the top left of the logical window, converted
to device coordinates. This is a notable exception to the rule that CDC member functions require logical coordinates.
32.2 The CScrollView SetScaleToFitSize Mode
The CScrollView class has a stretch-to-fit mode that displays the entire scrollable area in the view window. The
Windows MM_ANISOTROPIC mapping mode comes into play, with one restriction: positive y values always increase
in the down direction, as in MM_TEXT mode.
To use the stretch-to-fit mode, make the following call in your view's function in place of the call to SetScrollSizes:
SetScaleToFitSize(sizeTotal);
You can make this call in response to a Shrink To Fit menu command. Thus, the display can toggle between
scrolling mode and shrink-to-fit mode.
32.3 Using the Logical Twips Mapping Mode in a Scrolling View
The MFC CScrollView class allows you to specify only standard mapping modes. The EX19A example in Chapter 19
shows a new class CLogScrollView that accommodates the logical twips mode.
Chapter Six
33. The Modal Dialog and Windows Common Controls
Almost every Windows-based program uses a dialog window to interact with the user. The dialog might be a simple
OK message box, or it might be a complex data entry form. Calling this powerful element a dialog "box" is an
injustice. A dialog is truly a window that receives messages, that can be moved and closed, and that can even
accept drawing instructions in its client area.
The two kinds of dialogs are modal and modeless. This chapter explores the most common type, the modal dialog.
In the first of this chapter's two examples, you'll use all the familiar "old" controls, such as the edit control and the list
box, inherited from Win16. In the second example, you'll use the Windows common controls, which Microsoft
Windows 95 introduced. In Chapter 7 we'll take a look at the modeless dialog and the special-purpose Windows
common dialogs for opening files, selecting fonts, and so forth. In Chapter 8 we'll examine ActiveX Controls. Then
Chapter 9 discusses the new Internet Explorer control classes, introduced in MFC 6.0
34. Modal vs. Modeless Dialogs
The CDialog base class supports both modal and modeless dialogs. With a modal dialog, such as the Open File
dialog, the user cannot work elsewhere in the same application (more correctly, in the same user interface thread)
until the dialog is closed. With a modeless dialog, the user can work in another window in the application while the
dialog remains on the screen. Microsoft Word's Find and Replace dialog is a good example of a modeless dialog;
you can edit your document while the dialog is open.
- 57 -

Your choice of a modal or a modeless dialog depends on the application. Modal dialogs are much easier to program,
which might influence your decision.
FYI
The 16-bit versions of Windows support a special kind of modal dialog called a system modal dialog,
which prevents the user from switching to another application. Win32 also supports system modal
dialogs but with weird results: the user can switch to another application, but the dialog remains as
the top window. You probably don't want to use system modal dialogs in Win32 applications.
35. Resources and Controls
So now you know a dialog is a window. What makes the dialog different from the CView windows you've seen
already? For one thing, a dialog window is almost always tied to a Windows resource that identifies the dialog's
elements and specifies their layout. Because you can use the dialog editor (one of the resource editors) to create
and edit a dialog resource, you can quickly and efficiently produce dialogs in a visual manner.
A dialog contains a number of elements called controls. Dialog controls include edit controls (aka text boxes),
buttons, list boxes, combo boxes, static text (aka labels), tree views, progress indicators, sliders, and so forth.
Windows manages these controls using special grouping and tabbing logic, and that relieves you of a major
programming burden. The dialog controls can be referenced either by a CWnd pointer (because they are really
windows) or by an index number (with an associated #define constant) assigned in the resource. A control sends a
message to its parent dialog in response to a user action such as typing text or clicking a button.
The Microsoft Foundation Class (MFC) Library and ClassWizard work together to enhance the dialog logic that
Windows provides. ClassWizard generates a class derived from CDialog and then lets you associate dialog class
data members with dialog controls. You can specify editing parameters such as maximum text length and numeric
high and low limits. ClassWizard generates statements that call the MFC data exchange and data validation
functions to move information back and forth between the screen and the data members.
36. Programming a Modal Dialog
Modal dialogs are the most frequently used dialogs. A user action (a menu choice, for example) brings up a dialog on
the screen, the user enters data in the dialog, and then the user closes the dialog. Here's a summary of the steps to
add a modal dialog to an existing project:
Use the dialog editor to create a dialog resource that contains various controls. The dialog editor updates the
project's resource script (RC) file to include your new dialog resource, and it updates the project's resource.h
file with corresponding #define constants.
Use ClassWizard to create a dialog class that is derived from CDialog and attached to the resource created in
step 1. ClassWizard adds the associated code and header file to the Microsoft Visual C++ project.

When ClassWizard generates your derived dialog class, it generates a constructor that invokes a
CDialog modal constructor, which takes a resource ID as a parameter. Your generated dialog
header file contains a class enumerator constant IDD that is set to the dialog resource ID. In the
CPP file, the constructor implementation looks like this:
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/)
: CDialog(CMyDialog::IDD, pParent)
{
// initialization code here
}
The use of enum IDD decouples the CPP file from the resource IDs that are defined in the project's
resource.h file.
Use ClassWizard to add data members, exchange functions, and validation functions to the dialog class.
Use ClassWizard to add message handlers for the dialog's buttons and other event-generating controls.
Write the code for special control initialization (in OnInitDialog) and for the message handlers. Be sure the
CDialog virtual member function OnOK is called when the user closes the dialog (unless the user cancels the
dialog). (Note: OnOK is called by default.)
Write the code in your view class to activate the dialog. This code consists of a call to your dialog class's
constructor followed by a call to the DoModal dialog class member function. DoModal returns only when the
user exits the dialog window.
Now we'll proceed with a real example, one step at a time.
37. The Dialog That Ate Cincinnati—The EX06A Example
Let's not mess around with wimpy little dialogs. We'll build a monster dialog that contains almost every kind of
control. The job will be easy because Visual C++'s dialog editor is there to help us. The finished product is shown in
Figure 6-1.
- 58 -

Figure 6-1. The finished dialog in action.


As you can see, the dialog supports a human resources application. These kinds of business programs are fairly
boring, so the challenge is to produce something that could not have been done with 80-column punched cards. The
program is brightened a little by the use of scroll bar controls for "Loyalty" and "Reliability." Here is a classic example
of direct action and visual representation of data! ActiveX controls could add more interest, but you'll have to wait
until Chapter 8 for details on ActiveX.
37.1 Building the Dialog Resource
Here are the steps for building the dialog resource:
Run AppWizard to generate a project called EX06A. Choose New from Visual C++'s File menu, and then click
the Projects tab and select MFC AppWizard (exe). Accept all the defaults but two: select Single Document and
deselect Printing And Print Preview. The options and the default class names are shown here.

As usual, AppWizard sets the new project as the current project.


- 59 -

Create a new dialog resource with ID IDD_DIALOG1. Choose Resource from Visual C++'s Insert menu. The
Insert Resource dialog appears. Click on Dialog, and then click New. Visual C++ creates a new dialog
resource, as shown here.

The dialog editor assigns the resource ID IDD_DIALOG1 to the new dialog. Notice that the dialog editor inserts
OK and Cancel buttons for the new dialog.
Size the dialog and assign a caption. Enlarge the dialog box to about 5-by-7 inches.
When you right-click on the new dialog and choose Properties from the pop-up menu, the Dialog Properties
dialog appears. Type in the caption for the new dialog as shown in the screen below. The state of the pushpin
button in the upper-left corner determines whether the Dialog Properties dialog stays on top of other windows.
(When the pushpin is "pushed," the dialog stays on top of other windows.) Click the Toggle Grid button (on the
Dialog toolbar) to reveal the grid and to help align controls.

Set the dialog style. Click on the Styles tab at the top of the Dialog Properties dialog, and then set the style
properties as shown in the following illustration.
- 60 -

Set additional dialog styles. Click on the More Styles tab at the top of the Dialog Properties dialog, and then set
the style properties as shown here.

Add the dialog's controls. Use the control palette to add each control. (If the control palette is not visible, right-
click any toolbar and choose Controls from the list.) Drag controls from the control palette to the new dialog,
and then position and size the controls, as shown in Figure 6-1. Here are the control palette's controls.

The dialog editor displays the position and size of each control in the status bar. The position
units are special "dialog units," or DLUs, not device units. A horizontal DLU is the average
width of the dialog font divided by 4. A vertical DLU is the average height of the font divided by
8. The dialog font is normally 8-point MS Sans Serif.
Here's a brief description of the dialog's controls:
The static text control for the Name field. A static text control simply paints characters on the screen. No user
interaction occurs at runtime. You can type the text after you position the bounding rectangle, and you can
resize the rectangle as needed. This is the only static text control you'll see listed in text, but you should also
create the other static text controls as shown earlier in Figure 6-1. Follow the same procedure for the other
static text controls in the dialog. All static text controls have the same ID, but that doesn't matter because the
program doesn't need to access any of them.
The Name edit control. An edit control is the primary means of entering text in a dialog. Right-click the
control, and then choose Properties. Change this control's ID from IDC_EDIT1 to IDC_NAME. Accept the
defaults for the rest of the properties. Notice that the default sets Auto HScroll, which means that the text
scrolls horizontally when the box is filled.
The SS Nbr (social security number) edit control. As far as the dialog editor is concerned, the SS Nbr control
is exactly the same as the Name edit control. Simply change its ID to IDC_SSN. Later you will use
ClassWizard to make this a numeric field.
- 61 -

The Bio (biography) edit control. This is a multiline edit control. Change its ID to IDC_BIO, and then set its
properties as shown here.

The Category group box. This control serves only to group two radio buttons visually. Type in the caption
Category. The default ID is sufficient.
The Hourly and Salary radio buttons. Position these radio buttons inside the Category group box. Set the
Hourly button's ID to IDC_CAT and set the other properties as shown here.

Be sure that both buttons have the Auto property (the default) on the Styles tab set and that only the Hourly
button has the Group property set. When these properties are set correctly, Windows ensures that only one
of the two buttons can be selected at a time. The Category group box has no effect on the buttons'
operation.
The Insurance group box. This control holds three check boxes. Type in the caption Insurance.

Later, when you set the dialog's tab order, you'll ensure that the Insurance group box follows
the last radio button of the Category group. Set the Insurance control's Group property now
in order to "terminate" the previous group. If you fail to do this, it isn't a serious problem, but
you'll get several warning messages when you run the program through the debugger.
The Life, Disability, and Medical check boxes. Place these controls inside the Insurance group box. Accept
the default properties, but change the IDs to IDC_LIFE, IDC_DIS, and IDC_MED. Unlike radio buttons,
check boxes are independent; the user can set any combination.
The Skill combo box. This is the first of three types of combo boxes. Change the ID to IDC_SKILL, and then
click on the Styles tab and set the Type option to Simple. Click on the Data tab, and add three skills
(terminating each line with Ctrl-Enter) in the Enter Listbox Items box.
- 62 -

This is a combo box of type Simple. The user can type anything in the top edit control, use the mouse to
select an item from the attached list box, or use the Up or Down direction key to select an item from the
attached list box.
The Educ (education) combo box. Change the ID to IDC_EDUC; otherwise, accept the defaults. Add the
three education levels in the Data page, as shown in Figure 6-1. In this Dropdown combo box, the user can
type anything in the edit box, click on the arrow, and then select an item from the drop-down list box or use
the Up or Down direction key to select an item from the attached list box.
Aligning Controls
To align two or more controls, select the controls by clicking on the first control and then
Shift-clicking on the other controls you want to align. Next choose one of the alignment
commands (Left, Horiz.Center, Right, Top, Vert.Center, or Bottom) from the Align submenu
on the dialog editor's Layout menu.

To set the size for the drop-down portion of a combo box, click on the box's arrow and drag
down from the center of the bottom of the rectangle.
The Dept (department) list box. Change the ID to IDC_DEPT; otherwise, accept all the defaults. In this list
box, the user can select only a single item by using the mouse, by using the Up or Down direction key, or by
typing the first character of a selection. Note that you can't enter the initial choices in the dialog editor. You'll
see how to set these choices later.
The Lang (language) combo box. Change the ID to IDC_LANG, and then click on the Styles tab and set the
Type option to Drop List. Add three languages (English, French, and Spanish) in the Data page. With this
Drop List combo box, the user can select only from the attached list box. To select, the user can click on the
arrow and then select an entry from the drop-down list, or the user can type in the first letter of the selection
and then refine the selection using the Up or Down direction key.
The Loyalty and Reliability scroll bars. Do not confuse scroll bar controls with a window's built-in scroll bars
as seen in scrolling views. A scroll bar control behaves in the same manner as do other controls and can be
resized at design time. Position and size the horizontal scroll bar controls as shown previously in Figure 6-1,
and then assign the IDs IDC_LOYAL and IDC_RELY.
Selecting a Group of Controls
To quickly select a group of controls, position the mouse cursor above and to the left of the
group. Hold down the left mouse button and drag to a point below and to the right of the
group, as shown here.

The OK, Cancel, and Special pushbuttons. Be sure the button captions are OK, Cancel, and Special, and
then assign the ID IDC_SPECIAL to the Special button. Later you'll learn about special meanings that are
associated with the default IDs IDOK and IDCANCEL.
Any icon. (The MFC icon is shown as an example.) You can use the Picture control to display any icon or
bitmap in a dialog, as long as the icon or bitmap is defined in the resource script. We'll use the program's
- 63 -

MFC icon, identified as IDR_MAINFRAME. Set the Type option to Icon, and set the Image option to
IDR_MAINFRAME. Leave the ID as IDC_STATIC.
Check the dialog's tabbing order. Choose Tab Order from the dialog editor's Layout menu. Use the mouse to
set the tabbing order shown below. Click on each control in the order shown, and then press Enter.

If you mess up the tab sequence partway through, you can recover with a Ctrl-left mouse click
on the last correctly sequenced control. Subsequent mouse clicks will start with the next
sequence number.

A static text control (such as Name or Skill) has an ampersand (&) embedded in the text for its
caption. At runtime, the ampersand will appear as an underscore under the character that
follows. (See Figure 6-1.) This enables the user to jump to selected controls by holding down
the Alt key and pressing the key corresponding to the underlined character. (The related
control must immediately follow the static text in the tabbing order.) Thus, Alt-N jumps to the
Name edit control and Alt-K jumps to the Skill combo box. Needless to say, designated jump
characters should be unique within the dialog. The Skill control uses Alt-K because the SS Nbr
control uses Alt-S.
Save the resource file on disk. For safety, choose Save from the File menu or click the Save button on the
toolbar to save ex06a.rc. Keep the dialog editor running, and keep the newly built dialog on the screen.
37.2 ClassWizard and the Dialog Class
You have now built a dialog resource, but you can't use it without a corresponding dialog class. (The section titled
"Understanding the EX06A Application" explains the relationship between the dialog window and the underlying
classes.) ClassWizard works in conjunction with the dialog editor to create that class as follows:
Choose ClassWizard from Visual C++'s View menu (or press Ctrl-W). Be sure that you still have the newly built
dialog, IDD_DIALOG1, selected in the dialog editor and that EX06A is the current Visual C++ project.
Add the CEx06aDialog class. ClassWizard detects the fact that you've just created a dialog resource without an
associated C++ class. It politely asks whether you want to create a class, as shown below.
- 64 -

Accept the default selection of Create A New Class, and click OK. Fill in the top field of the New Class dialog,
as shown here.

Add the CEx06aDialog variables. After ClassWizard creates the CEx06aDialog class, the MFC ClassWizard
dialog appears. Click on the Member Variables tab, and the Member Variables page appears, as shown here.
- 65 -

You need to associate data members with each of the dialog's controls. To do this, click on a control ID and
then click the Add Variable button. The Add Member Variable dialog appears, as shown in the following
illustration.

Type in the member variable name, and choose the variable type according to the following table. Be sure to
type in the member variable name exactly as shown; the case of each letter is important. When you're done,
click OK to return to the MFC ClassWizard dialog. Repeat this process for each of the listed controls.
Control ID Data Member Type

IDC_BIO m_strBio CString

IDC_CAT m_nCat int

IDC_DEPT m_strDept CString

IDC_DIS m_bInsDis BOOL

IDC_EDUC m_strEduc CString

IDC_LANG m_nLang CString

IDC_LIFE m_bInsLife BOOL

IDC_LOYAL m_nLoyal int

IDC_MED m_bInsMed BOOL

IDC_NAME m_strName CString

IDC_RELY m_nRely int

IDC_SKILL m_strSkill CString

IDC_SSN m_nSsn int


As you select controls in the MFC ClassWizard dialog, various edit boxes appear at the bottom of the dialog. If
you select a CString variable, you can set its maximum number of characters; if you select a numeric variable,
you can set its high and low limits. Set the minimum value for IDC_SSN to 0 and the maximum value to
999999999.
Most relationships between control types and variable types are obvious. The way in which radio buttons
correspond to variables is not so intuitive, however. The CDialog class associates an integer variable with each
radio button group, with the first button corresponding to value 0, the second to 1, and so forth.
Add the message-handling function for the Special button. CEx06aDialog doesn't need many message-
handling functions because the CDialog base class, with the help of Windows, does most of the dialog
management. When you specify the ID IDOK for the OK button (ClassWizard's default), for example, the virtual
- 66 -

CDialog function OnOK gets called when the user clicks the button. For other buttons, however, you need
message handlers.
Click on the Message Maps tab. The ClassWizard dialog should contain an entry for IDC_SPECIAL in the
Object IDs list box. Click on this entry, and double-click on the BN_CLICKED message that appears in the
Messages list box. ClassWizard invents a member function name, OnSpecial, and opens the Add Member
Function dialog, as shown here.

You could type in your own function name here, but this time accept the default and click OK. Click the Edit
Code button in the MFC ClassWizard dialog. This opens the file ex06aDialog.cpp and moves to the OnSpecial
function. Insert a TRACE statement in the OnSpecial function by typing in the boldface code, shown below,
which replaces the existing code:
void CEx06aDialog::OnSpecial()
{
TRACE("CEx06aDialog::OnSpecial\n");
}
Use ClassWizard to add an OnInitDialog message-handling function. As you'll see in a moment, ClassWizard
generates code that initializes a dialog's controls. This DDX (Dialog Data Exchange) code won't initialize the
list-box choices, however, so you must override the CDialog::OnInit-Dialog function. Although OnInitDialog is a
virtual member function, ClassWizard generates the prototype and skeleton if you map the WM_INITDIALOG
message in the derived dialog class. To do so, click on CEx06aDialog in the Object IDs list box and then
double-click on the WM_INITDIALOG message in the Messages list box. Click the Edit Code button in the MFC
ClassWizard dialog to edit the OnInitDialog function. Type in the boldface code, which replaces the existing
code:
BOOL CEx06aDialog::OnInitDialog()
{
// Be careful to call CDialog::OnInitDialog
// only once in this function
CListBox* pLB = (CListBox*) GetDlgItem(IDC_DEPT);
pLB->InsertString(-1, "Documentation");
pLB->InsertString(-1, "Accounting");
pLB->InsertString(-1, "Human Relations");
pLB->InsertString(-1, "Security");

// Call after initialization


return CDialog::OnInitDialog();
}
You could also use the same initialization technique for the combo boxes, in place of the initialization in the
resource.
37.3 Connecting the Dialog to the View
Now we've got the resource and the code for a dialog, but it's not connected to the view. In most applications, you
would probably use a menu choice to activate a dialog, but we haven't studied menus yet. Here we'll use the familiar
mouse-click message WM_LBUTTONDOWN to start the dialog. The steps are as follows:
In ClassWizard, select the CEx06aView class. At this point, be sure that EX06A is Visual C++'s current project.
Use ClassWizard to add the OnLButtonDown member function. You've done this in the examples in earlier
chapters. Simply select the CEx06aView class name, click on the CEx06aView object ID, and then double-click
on WM_LBUTTONDOWN.
Write the code for OnLButtonDown in file ex06aView.cpp. Add the boldface code below. Most of the code
consists of TRACE statements to print the dialog data members after the user exits the dialog. The
CEx06aDialog constructor call and the DoModal call are the critical statements, however:
void CEx06aView::OnLButtonDown(UINT nFlags, CPoint point)
- 67 -

{
CEx06aDialog dlg;
dlg.m_strName = "Shakespeare, Will";
dlg.m_nSsn = 307806636;
dlg.m_nCat = 1; // 0 = hourly, 1 = salary
dlg.m_strBio = "This person is not a well-motivated tech writer";
dlg.m_bInsLife = TRUE;
dlg.m_bInsDis = FALSE;
dlg.m_bInsMed = TRUE;
dlg.m_strDept = "Documentation";
dlg.m_strSkill = "Writer";
dlg.m_nLang = 0;
dlg.m_strEduc = "College";
dlg.m_nLoyal = dlg.m_nRely = 50;
int ret = dlg.DoModal();
TRACE("DoModal return = %d\n", ret);
TRACE("name = %s, ssn = %d, cat = %d\n",
dlg.m_strName, dlg.m_nSsn, dlg.m_nCat);
TRACE("dept = %s, skill = %s, lang = %d, educ = %s\n",
dlg.m_strDept, dlg.m_strSkill, dlg.m_nLang, dlg.m_strEduc);
TRACE("life = %d, dis = %d, med = %d, bio = %s\n",
dlg.m_bInsLife, dlg.m_bInsDis, dlg.m_bInsMed, dlg.m_strBio);
TRACE("loyalty = %d, reliability = %d\n",
dlg.m_nLoyal, dlg.m_nRely);
}
Add code to the virtual OnDraw function in file ex06aView.cpp. To prompt the user to press the left mouse
button, code the CEx06aView::OnDraw function. (The skeleton was generated by AppWizard.) The following
boldface code (which you type in) replaces the existing code:
void CEx06aView::OnDraw(CDC* pDC)
{
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
To ex06aView.cpp, add the dialog class include statement. The OnLButtonDown function above depends on
the declaration of class CEx06aDialog. You must insert the include statement
#include "ex06aDialog.h"
at the top of the CEx06aView class source code file (ex06aView.cpp), after the statement
#include "ex06aView.h"
Build and test the application. If you have done everything correctly, you should be able to build and run the
EX06A application through Visual C++. Try entering data in each control, and then click the OK button and
observe the TRACE results in the Debug window. Notice that the scroll bar controls don't do much yet; we'll
attend to them later. Notice what happens when you press Enter while typing in text data in a control: the dialog
closes immediately.
37.4 Understanding the EX06A Application
When your program calls DoModal, control is returned to your program only when the user closes the dialog. If you
understand that, you understand modal dialogs. When you start creating modeless dialogs, you'll begin to appreciate
the programming simplicity of modal dialogs. A lot happens "out of sight" as a result of that DoModal call, however.
Here's a "what calls what" summary:
CDialog::DoModal
CEx06aDialog::OnInitDialog
…additional initialization…
CDialog::OnInitDialog
CWnd::UpdateData(FALSE)
CEx06aDialog::DoDataExchange
user enters data…
user clicks the OK button
CEx06aDialog::OnOK
…additional validation…
- 68 -

CDialog::OnOK
CWnd::UpdateData(TRUE)
CEx06aDialog::DoDataExchange
CDialog::EndDialog(IDOK)
OnInitDialog and DoDataExchange are virtual functions overridden in the CEx06aDialog class. Windows calls
OnInitDialog as part of the dialog initialization process, and that results in a call to DoDataExchange, a CWnd virtual
function that was overridden by ClassWizard. Here is a listing of that function:
void CEx06aDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CEx06aDialog)
DDX_Text(pDX, IDC_BIO, m_strBio);
DDX_Radio(pDX, IDC_CAT, m_nCat);
DDX_LBString(pDX, IDC_DEPT, m_strDept);
DDX_Check(pDX, IDC_DIS, m_bInsDis);
DDX_CBString(pDX, IDC_EDUC, m_strEduc);
DDX_CBIndex(pDX, IDC_LANG, m_nLang);
DDX_Check(pDX, IDC_LIFE, m_bInsLife);
DDX_Scroll(pDX, IDC_LOYAL, m_nLoyal);
DDX_Check(pDX, IDC_MED, m_bInsMed);
DDX_Text(pDX, IDC_NAME, m_strName);
DDX_Scroll(pDX, IDC_RELY, m_nRely);
DDX_CBString(pDX, IDC_SKILL, m_strSkill);
DDX_Text(pDX, IDC_SSN, m_nSsn);
DDV_MinMaxInt(pDX, m_nSsn, 0, 999999999);
//}}AFX_DATA_MAP
}
The DoDataExchange function and the DDX_ (exchange) and DDV_ (validation) functions are "bidirectional." If
UpdateData is called with a FALSE parameter, the functions transfer data from the data members to the dialog
controls. If the parameter is TRUE, the functions transfer data from the dialog controls to the data members.
DDX_Text is overloaded to accommodate a variety of data types.
The EndDialog function is critical to the dialog exit procedure. DoModal returns the parameter passed to EndDialog.
IDOK accepts the dialog's data, and IDCANCEL cancels the dialog.

You can write your own "custom" DDX function and wire it into Visual C++. This feature is useful if
you're using a unique data type throughout your application. See MFC Technical Note #26 in the
online documentation.
38. Enhancing the Dialog Program
The EX06A program required little coding for a lot of functionality. Now we'll make a new version of this program that
uses some hand-coding to add extra features. We'll eliminate EX06A's rude habit of dumping the user in response to
a press of the Enter key, and we'll hook up the scroll bar controls.
38.1 Taking Control of the OnOK Exit
In the original EX06A program, the CDialog::OnOK virtual function handled the OK button, which triggered data
exchange and the exit from the dialog. Pressing the Enter key happens to have the same effect, and that might or
might not be what you want. If the user presses Enter while in the Name edit control, for example, the dialog closes
immediately.
What's going on here? When the user presses Enter, Windows looks to see which pushbutton has the input focus,
as indicated on the screen by a dotted rectangle. If no button has the focus, Windows looks for the default
pushbutton that the program or the resource specifies. (The default pushbutton has a thicker border.) If the dialog
has no default button, the virtual OnOK function is called, even if the dialog does not contain an OK button.
You can disable the Enter key by writing a do-nothing CEx06aDialog::OnOK function and adding the exit code to a
new function that responds to clicking the OK button. Here are the steps:
Use ClassWizard to "map" the IDOK button to the virtual OnOK function. In ClassWizard, choose IDOK from
the CEx06aDialog Object IDs list, and then double-click on BN_CLICKED. This generates the prototype and
skeleton for OnOK.
Use the dialog editor to change the OK button ID. Select the OK button, change its ID from IDOK to IDC_OK,
and then uncheck its Default Button property. Leave the OnOK function alone.
- 69 -

Use ClassWizard to create a member function called OnClickedOk. This CEx06aDialog class member function
is keyed to the BN_CLICKED message from the newly renamed control IDC_OK.
Edit the body of the OnClickedOk function in ex06aDialog.cpp. This function calls the base class OnOK
function, as did the original CEx06aDialog::OnOK function. Here is the code:
void CEx06aDialog::OnClickedOk()
{
TRACE("CEx06aDialog::OnClickedOk\n");
CDialog::OnOK();
}
Edit the original OnOK function in ex06aDialog.cpp. This function is a "leftover" handler for the old IDOK button.
Edit the code as shown here:
void CEx06aDialog::OnOK()
{
// dummy OnOK function -- do NOT call CDialog::OnOK()
TRACE("CEx06aDialog::OnOK\n");
}
Build and test the application. Try pressing the Enter key now. Nothing should happen, but TRACE output
should appear in the Debug window. Clicking the OK button should exit the dialog as before, however.
For Win32 Programmers
Dialog controls send WM_ COMMAND notification messages to their parent dialogs. For a single
button click, for example, the bottom 16 bits of wParam contain the button ID, the top 16 bits of
wParam contain the BN_CLICKED notification code, and lParam contains the button handle. Most
window procedure functions process these notification messages with a nested switch statement.
MFC "flattens out" the message processing logic by "promoting" control notification messages to the
same level as other Windows messages.
For a Delete button (for example), ClassWizard generates notification message map entries similar
to these:
ON_BN_CLICKED(IDC_DELETE, OnDeleteClicked)
ON_BN_DOUBLECLICKED(IDC_DELETE, OnDeleteDblClicked)
Button events are special because they generate command messages if your dialog class doesn't
have notification handlers like the ones above. As Chapter 13 explains, the application framework
"routes" these command messages to various objects in your application. You could also map the
control notifications with a more generic ON_ COMMAND message-handling entry like this:
ON_COMMAND(IDC_DELETE, OnDelete)
In this case, the OnDelete function is unable to distinguish between a single click and a double click,
but that's no problem because few Windows-based programs utilize double clicks for buttons.
38.2 OnCancel Processing
Just as pressing the Enter key triggers a call to OnOK, pressing the Esc key triggers a call to OnCancel, which
results in an exit from the dialog with a DoModal return code of IDCANCEL. EX06A does no special processing for
IDCANCEL; therefore, pressing the Esc key (or clicking the Close button) closes the dialog. You can circumvent this
process by substituting a dummy OnCancel function, following approximately the same procedure you used for the
OK button.
38.3 Hooking Up the Scroll Bar Controls
The dialog editor allows you to include scroll bar controls in your dialog, and ClassWizard lets you add integer data
members. You must add code to make the Loyalty and Reliability scroll bars work.
Scroll bar controls have position and range values that can be read and written. If you set the range to (0, 100), for
example, a corresponding data member with a value of 50 positions the scroll box at the center of the bar. (The
function CScrollBar::SetScrollPos also sets the scroll box position.) The scroll bars send the WM_ HSCROLL and
WM_ VSCROLL messages to the dialog when the user drags the scroll box or clicks the arrows. The dialog's
message handlers must decode these messages and position the scroll box accordingly.
Each control you've seen so far has had its own individual message handler function. Scroll bar controls are different
because all horizontal scroll bars in a dialog are tied to a single WM_HSCROLL message handler and all vertical
scroll bars are tied to a single WM_VSCROLL handler. Because this monster dialog contains two horizontal scroll
bars, the single WM_ HSCROLL message handler must figure out which scroll bar sent the scroll message.
Here are the steps for adding the scroll bar logic to EX06A:
Add the class enum statements for the minimum and maximum scroll range. In ex06aDialog.h, add the
following lines at the top of the class declaration:
- 70 -

enum { nMin = 0 };
enum { nMax = 100 };
Edit the OnInitDialog function to initialize the scroll ranges. In the OnInitDialog function, we'll set the minimum
and the maximum scroll values such that the CEx06aDialog data members represent percentage values. A
value of 100 means "Set the scroll box to the extreme right"; a value of 0 means "Set the scroll box to the
extreme left."
Add the following code to the CEx06aDialog member function OnInitDialog in the file ex06aDialog.cpp:
CScrollBar* pSB = (CScrollBar*) GetDlgItem(IDC_LOYAL);
pSB->SetScrollRange(nMin, nMax);

pSB = (CScrollBar*) GetDlgItem(IDC_RELY);


pSB->SetScrollRange(nMin, nMax);
Use ClassWizard to add a scroll bar message handler to CEx06aDialog. Choose the WM_HSCROLL message,
and then add the member function OnHScroll. Enter the following boldface code:
void CEx06aDialog::OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
int nTemp1, nTemp2;

nTemp1 = pScrollBar->GetScrollPos();
switch(nSBCode) {
case SB_THUMBPOSITION:
pScrollBar->SetScrollPos(nPos);
break;
case SB_LINELEFT: // left arrow button
nTemp2 = (nMax - nMin) / 10;
if ((nTemp1 - nTemp2) > nMin) {
nTemp1 -= nTemp2;
}
else {
nTemp1 = nMin;
}
pScrollBar->SetScrollPos(nTemp1);
break;
case SB_LINERIGHT: // right arrow button
nTemp2 = (nMax - nMin) / 10;
if ((nTemp1 + nTemp2) < nMax) {
nTemp1 += nTemp2;
}
else {
nTemp1 = nMax;
}
pScrollBar->SetScrollPos(nTemp1);
break;
}
}

The scroll bar functions use 16-bit integers for both range and position.
Build and test the application. Build and run EX06A again. Do the scroll bars work this time? The scroll boxes
should "stick" after you drag them with the mouse, and they should move when you click the scroll bars' arrows.
(Notice that we haven't added logic to cover the user's click on the scroll bar itself.)
39. Identifying Controls: CWnd Pointers and Control IDs
When you lay out a dialog resource in the dialog editor, you identify each control by an ID such as IDC_SSN. In your
program code, however, you often need access to a control's underlying window object. The MFC library provides
the CWnd::GetDlgItem function for converting an ID to a CWnd pointer. You've seen this already in the OnInitDialog
- 71 -

member function of class CEx06aDialog. The application framework "manufactured" this returned CWnd pointer
because there never was a constructor call for the control objects. This pointer is temporary and should not be stored
for later use.

If you need to convert a CWnd pointer to a control ID, use the MFC library GetDlgCtrlID member
function of classCWnd.
40. Setting the Color for the Dialog Background and for Controls
You can change the background color of individual dialogs or specific controls in a dialog, but you have to do some
extra work. The parent dialog is sent a WM_CTLCOLOR message for each control immediately before the control is
displayed. A WM_CTLCOLOR message is also sent on behalf of the dialog itself. If you map this message in your
derived dialog class, you can set the foreground and background text colors and select a brush for the control or
dialog nontext area.
Following is a sample OnCtlColor function that sets all edit control backgrounds to yellow and the dialog background
to red. The m_hYellowBrush and m_hRedBrush variables are data members of type HBRUSH, initialized in the
dialog's OnInitDialog function. The nCtlColor parameter indicates the type of control, and the pWnd parameter
identifies the specific control. If you wanted to set the color for an individual edit control, you would convert pWnd to a
child window ID and test it.
HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
if (nCtlColor == CTLCOLOR_EDIT) {
pDC->SetBkColor(RGB(255, 255, 0)); // yellow
return m_hYellowBrush;
}
if (nCtlColor == CTLCOLOR_DLG) {
pDC->SetBkColor(RGB(255, 0, 0)); // red
return m_hRedBrush;
}
return CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
}

The dialog does not post the WM_CTLCOLOR message in the message queue; instead, it calls the
Win32 SendMessage function to send the message immediately. Thus the message handler can
return a parameter, in this case a handle to a brush. This is not an MFC CBrush object but rather a
Win32 HBRUSH. You can create the brush by calling the Win32 functions CreateSolidBrush,
CreateHatchBrush, and so forth.
For Win32 Programmers
Actually, Win32 no longer has a WM_CTLCOLOR message. It was replaced by control-specific
messages such as WM_CTLCOLORBTN, WM_CTLCOLORDLG, and so on. MFC and ClassWizard
process these messages invisibly, so your programs look as though they're mapping the old 16-bit
WM_CTLCOLOR messages. This trick makes debugging more complex, but it makes portable code
easier to write. Another option would be to use the ON_MESSAGE macro to map the real Win32
messages.
If your dialog class (or other MFC window class) doesn't map the WM_CTLCOLOR message, the
framework reflects the message back to the control. When you study window subclassing in Chapter
16, you'll learn how to write your own control window classes that can process these reflected
messages.
41. Painting Inside the Dialog Window
You can paint directly in the client area of the dialog window, but you'll avoid overwriting dialog elements if you paint
only inside a control window. If you want to display text only, use the dialog editor to create a blank static control with
a unique ID and then call the CWnd::SetDlgItemText function in a dialog member function such as OnInitDialog to
place text in the control.
Displaying graphics is more complicated. You must use ClassWizard to add an OnPaint member function to the
dialog; this function must convert the static control's ID to a CWnd pointer and get its device context. The trick is to
draw inside the control window while preventing Windows from overwriting your work later. The
Invalidate/UpdateWindow sequence achieves this. Here is an OnPaint function that paints a small black square in a
static control:
void CMyDialog::OnPaint()
- 72 -

{
CWnd* pWnd = GetDlgItem(IDC_STATIC1); // IDC_STATIC1 specified
// in the dialog editor
CDC* pControlDC = pWnd->GetDC();

pWnd->Invalidate();
pWnd->UpdateWindow();
pControlDC->SelectStockObject(BLACK_BRUSH);
pControlDC->Rectangle(0, 0, 10, 10); // black square bullet
pWnd->ReleaseDC(pControlDC);
}
As with all windows, the dialog's OnPaint function is called only if some part of the dialog is invalidated. You can
force the OnPaint call from another dialog member function with the following statement:
Invalidate();
42. Adding Dialog Controls at Runtime
You've seen how to use the resource editor to create dialog controls at build time. If you need to add a dialog control
at runtime, here are the programming steps:
Add an embedded control window data member to your dialog class. The MFC control window classes include
CButton, CEdit, CListBox, and CComboBox. An embedded control C++ object is constructed and destroyed
along with the dialog object.
Choose Resource Symbols from Visual C++'s View menu. Add an ID constant for the new control.
Use ClassWizard to map the WM_INITDIALOG message, thus overriding CDialog::OnInitDialog. This function
should call the embedded control window's Create member function. This call displays the new control in the
dialog. Windows will destroy the control window when it destroys the dialog window.
In your derived dialog class, manually add the necessary notification message handlers for your new control.
In Chapter 13, you'll be adding a rich edit control to a view at runtime.
43. Using Other Control Features
You've seen how to customize the control class CScrollBar by adding code in the dialog's OnInitDialog member
function. You can program other controls in a similar fashion. In the Microsoft Visual C++ MFC Library Reference, or
in the online help under "Microsoft Foundation Class Libary and Templates," look at the control classes, particularly
CListBox and CComboBox. Each has a number of features that ClassWizard does not directly support. Some combo
boxes, for example, can support multiple selections. If you want to use these features, don't try to use ClassWizard
to add data members. Instead, define your own data members and add your own exchange code in OnInitDialog and
OnClickedOK.
For Win32 Programmers
If you've programmed controls in Win32, you'll know that parent windows communicate to controls
via Windows messages. So what does a function such as CListBox::InsertString do? (You've seen
this function called in your OnInitDialog function.) If you look at the MFC source code, you'll see that
InsertString sends an LB_INSERTSTRING message to the designated list-box control. Other control
class member functions don't send messages because they apply to all window types. The
CScrollView::SetScrollRange function, for example, calls the Win32 SetScrollRange function,
specifying the correct hWnd as a parameter.
44. Windows Common Controls
The controls you used in EX06A are great learning controls because they're easy to program. Now you're ready for
some more "interesting" controls. We'll take a look at some important new Windows controls, introduced for Microsoft
Windows 95 and available in Microsoft Windows NT. These include the progress indicator, trackbar, spin button
control, list control, and tree control.
The code for these controls is in the Windows COMCTL32.DLL file. This code includes the window procedure for
each control, together with code that registers a window class for each control. The registration code is called when
the DLL is loaded. When your program initializes a dialog, it uses the symbolic class name in the dialog resource to
connect to the window procedure in the DLL. Thus your program owns the control's window, but the code is in the
DLL. Except for ActiveX controls, most controls work this way.
Example EX06B uses the aforementioned controls. Figure 6-2 shows the dialog from that example. Refer to it when
you read the control descriptions that follow.
Be aware that ClassWizard offers no member variable support for the common controls. You'll have to add code to
your OnInitDialog and OnOK functions to initialize and read control data. ClassWizard will, however, allow you to
map notification messages from common controls.
- 73 -

Figure 6-2. The Windows Common Controls Dialog example.


44.1 The Progress Indicator Control
The progress indicator is the easiest common control to program and is represented by the MFC CProgressCtrl
class. It is generally used only for output. This control, together with the trackbar, can effectively replace the scroll
bar controls you saw in the previous example. To initialize the progress indicator, call the SetRange and SetPos
member functions in your OnInitDialog function, and then call SetPos anytime in your message handlers. The
progress indicator shown in Figure 6-2 has a range of 0 to 100, which is the default range.
44.2 The Trackbar Control
The trackbar control (class CSliderCtrl), sometimes called a slider, allows the user to set an "analog" value.
(Trackbars would have been more effective than sliders for Loyalty and Reliability in the EX06A example.) If you
specify a large range for this control—0 to 100 or more, for example—the trackbar's motion appears continuous. If
you specify a small range, such as 0 to 5, the tracker moves in discrete increments. You can program tick marks to
match the increments. In this discrete mode, you can use a trackbar to set such items as the display screen
resolution, lens f-stop values, and so forth. The trackbar does not have a default range.
The trackbar is easier to program than the scroll bar because you don't have to map the WM_HSCROLL or
WM_VSCROLL messages in the dialog class. As long as you set the range, the tracker moves when the user slides
it or clicks in the body of the trackbar. You might choose to map the scroll messages anyway if you want to show the
position value in another control. The GetPos member function returns the current position value. The top trackbar in
Figure 6-2 operates continuously in the range 0 to 100. The bottom trackbar has a range of 0 to 4, and those indexes
are mapped to a series of double-precision values (4.0, 5.6, 8.0, 11.0, and 16.0).
44.3 The Spin Button Control
The spin button control (class CSpinButtonCtrl) is an itsy-bitsy scroll bar that's most often used in conjunction with an
edit control. The edit control, located just ahead of the spin control in the dialog's tabbing order, is known as the spin
control's buddy. The idea is that the user holds down the left mouse button on the spin control to raise or lower the
value in the edit control. The spin speed accelerates as the user continues to hold down the mouse button.
If your program uses an integer in the buddy, you can avoid C++ programming almost entirely. Just use ClassWizard
to attach an integer data member to the edit control, and set the spin control's range in the OnInitDialog function.
(You probably won't want the spin control's default range, which runs backward from a minimum of 100 to a
maximum of 0.) Don't forget to select Auto Buddy and Set Buddy Integer in the spin control's Styles property page.
You can call the SetRange and SetAccel member functions in your OnInitDialog function to change the range and
the acceleration profile.
If you want your edit control to display a noninteger, such as a time or a floating-point number, you must map the
spin control's WM_VSCROLL (or WM_HSCROLL) messages and write handler code to convert the spin control's
integer to the buddy's value.
44.4 The List Control
Use the list control (class CListCtrl) if you want a list that contains images as well as text. Figure 6-2 shows a list
control with a "list" view style and small icons. The elements are arranged in a grid, and the control includes
horizontal scrolling. When the user selects an item, the control sends a notification message, which you map in your
dialog class. That message handler can determine which item the user selected. Items are identified by a zero-based
integer index.
Both the list control and the tree control get their graphic images from a common control element called an image list
(class CImageList). Your program must assemble the image list from icons or bitmaps and then pass an image list
pointer to the list control. Your OnInitDialog function is a good place to create and attach the image list and to assign
text strings. The InsertItem member function serves this purpose.
- 74 -

List control programming is straightforward if you stick with strings and icons. If you implement drag and drop or if
you need custom owner-drawn graphics, you've got more work to do.
44.5 The Tree Control
You're already familiar with tree controls if you've used Microsoft Windows Explorer or Visual C++'s Workspace view.
The MFC CTreeCtrl class makes it easy to add this same functionality to your own programs. Figure 6-2 illustrates a
tree control that shows a modern American combined family. The user can expand and collapse elements by clicking
the + and - buttons or by double-clicking the elements. The icon next to each item is programmed to change when
the user selects the item with a single click.
The list control and the tree control have some things in common: they can both use the same image list, and they
share some of the same notification messages. Their methods of identifying items are different, however. The tree
control uses an HTREEITEM handle instead of an integer index. To insert an item, you call the InsertItem member
function, but first you must build up a TV_INSERTSTRUCT structure that identifies (among other things) the string,
the image list index, and the handle of the parent item (which is null for top-level items).
As with list controls, infinite customization possibilities are available for the tree control. For example, you can allow
the user to edit items and to insert and delete items.
44.6 The WM_NOTIFY Message
The original Windows controls sent their notifications in WM_COMMAND messages. The standard 32-bit wParam
and lParam message parameters are not sufficient, however, for the information that a common control needs to
send to its parent. Microsoft solved this "bandwidth" problem by defining a new message, WM_NOTIFY. With the
WM_NOTIFY message, wParam is the control ID and lParam is a pointer to an NMHDR structure, which is managed
by the control. This C structure is defined by the following code:
typedef struct tagNMHDR {
HWND hwndFrom; // handle to control sending the message
UINT idFrom; // ID of control sending the message
UINT code; // control-specific notification code
} NMHDR;
Many controls, however, send WM_NOTIFY messages with pointers to structures larger than NMHDR. Those
structures contain the three members above plus appended control-specific members. Many tree control
notifications, for example, pass a pointer to an NM_TREEVIEW structure that contains TV_ITEM structures, a drag
point, and so forth. When ClassWizard maps a WM_NOTIFY message, it generates a pointer to the appropriate
structure.
45. The EX06B Example
I won't try to contrive a business-oriented example that uses all the custom controls. I'll just slap the controls in a
modal dialog and trust that you'll see what's going on. The steps are shown below. After step 3, the instructions are
oriented to the individual controls rather than to the Visual C++ components you'll be using.
Run AppWizard to generate the EX06B project. Choose New from Visual C++'s File menu, and then select
Microsoft AppWizard (exe) from the Projects page. Accept all the defaults but two: select Single Document and
deselect Printing And Print Preview. The options and the default class names are shown here.
- 75 -

Create a new dialog resource with ID IDD_DIALOG1. Place the controls as shown back in Figure 6-2.
You can select the controls from the control palette. The following table lists the control types and their IDs.
Don't worry about the other properties now—you'll set those in the following steps. (Some controls might look
different than they do in Figure 6-2 until you set their properties.) Set the tab order as shown next.

Tab Sequence Control Type Child Window ID

1 Static IDC_STATIC

2 Progress IDC_PROGRESS1

3 Static IDC_STATIC

4 Trackbar (Slider) IDC_TRACKBAR1

5 Static IDC_STATIC_TRACK1

6 Static IDC_STATIC

7 Trackbar (Slider) IDC_TRACKBAR2


- 76 -

8 Static IDC_STATIC_TRACK2

9 Static IDC_STATIC

10 Edit IDC_BUDDY_SPIN1

11 Spin IDC_SPIN1

12 Static IDC_STATIC

13 Static IDC_STATIC

14 List control IDC_LISTVIEW1

15 Static IDC_STATIC_LISTVIEW1

16 Static IDC_STATIC

17 Tree control IDC_TREEVIEW1

18 Static IDC_STATIC_TREEVIEW1

19 Pushbutton IDOK

20 Pushbutton IDCANCEL
Use ClassWizard to create a new class, CEx06bDialog, derived from CDialog. ClassWizard will automatically
prompt you to create this class because it knows that the IDD_DIALOG1 resource exists without an associated
C++ class. Map the WM_INITDIALOG message, the WM_HSCROLL message, and the WM_VSCROLL
message.
Program the progress control. Because ClassWizard won't generate a data member for this control, you must
do it yourself. Add a public integer data member named m_nProgress in the CEx06bDialog class header, and
set it to 0 in the constructor. Also, add the following code in the OnInitDialog member function:
CProgressCtrl* pProg =
(CProgressCtrl*) GetDlgItem(IDC_PROGRESS1);
pProg->SetRange(0, 100);
pProg->SetPos(m_nProgress);

Program the "continuous" trackbar control. Add a public integer data member named m_nTrackbar1 to the
CEx06bDialog header, and set it to 0 in the constructor. Next add the following code in the OnInitDialog
member function to set the trackbar's range, to initialize its position from the data member, and to set the
neighboring static control to the tracker's current value.
CString strText1;
CSliderCtrl* pSlide1 =
(CSliderCtrl*) GetDlgItem(IDC_TRACKBAR1);
pSlide1->SetRange(0, 100);
pSlide1->SetPos(m_nTrackbar1);
strText1.Format("%d", pSlide1->GetPos());
SetDlgItemText(IDC_STATIC_TRACK1, strText1);
To keep the static control updated, you need to map the WM_HSCROLL message that the trackbar sends to
the dialog. Here is the code for the handler:
void CEx06bDialog::OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar;
CString strText;
strText.Format("%d", pSlide->GetPos());
SetDlgItemText(IDC_STATIC_TRACK1, strText);
}
Finally, you need to update the trackbar's m_nTrackbar1 data member when the user clicks OK. Your natural
instinct would be to put this code in the OnOK button handler. You would have a problem, however, if a data
- 77 -

exchange validation error occurred involving any other control in the dialog. Your handler would set
m_nTrackbar1 even though the user might choose to cancel the dialog. To avoid this problem, add your code in
the DoDataExchange function as shown below. If you do your own validation and detect a problem, call the
CDataExchange::Fail function, which alerts the user with a message box.
if (pDX->m_bSaveAndValidate) {
TRACE("updating trackbar data members\n");
CSliderCtrl* pSlide1 =
(CSliderCtrl*) GetDlgItem(IDC_TRACKBAR1);
m_nTrackbar1 = pSlide1->GetPos();

Program the "discrete" trackbar control. Add a public integer data member named m_nTrackbar2 to the
CEx06bDialog header, and set it to 0 in the constructor. This data member is a zero-based index into the
dValue, the array of numbers (4.0, 5.6, 8.0, 11.0, and 16.0) that the trackbar can represent. Define dValue as a
private static double array member variable in ex06bDialog.h, and add to ex06bDialog.cpp the following line:
double CEx06bDialog::dValue[5] = {4.0, 5.6, 8.0, 11.0, 16.0};
Next add code in the OnInitDialog member function to set the trackbar's range and initial position.
CString strText2;
CSliderCtrl* pSlide2 =
(CSliderCtrl*) GetDlgItem(IDC_TRACKBAR2);
pSlide2->SetRange(0, 4);
pSlide2->SetPos(m_nTrackbar2);
strText2.Format("%3.1f", dValue[pSlide2->GetPos()]);
SetDlgItemText(IDC_STATIC_TRACK2, strText2);
If you had only one trackbar, the WM_HSCROLL handler in step 5 would work. But because you have two
trackbars that send WM_HSCROLL messages, the handler must differentiate. Here is the new code:
void CEx06bDialog::OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar;
CString strText;

// Two trackbars are sending


// HSCROLL messages (different processing)
switch(pScrollBar->GetDlgCtrlID()) {
case IDC_TRACKBAR1:
strText.Format("%d", pSlide->GetPos());
SetDlgItemText(IDC_STATIC_TRACK1, strText);
break;
case IDC_TRACKBAR2:
strText.Format("%3.1f", dValue[pSlide->GetPos()]);
SetDlgItemText(IDC_STATIC_TRACK2, strText);
break;
}
}
This trackbar needs tick marks, so you must check the control's Tick Marks and Auto Ticks properties back in
the dialog editor. With Auto Ticks set, the trackbar will place a tick at every increment. The same data
exchange considerations applied to the previous trackbar apply to this trackbar. Add the following code in the
dialog class DoDataExchange member function inside the block for the if statement you added in the previous
step:
CSliderCtrl* pSlide2 =
(CSliderCtrl*) GetDlgItem(IDC_TRACKBAR2);
m_nTrackbar2 = pSlide2->GetPos();
Use the dialog editor to set the Point property of both trackbars to Bottom/Right. Select Right for the Align Text
property of both the IDC_STATIC_TRACK1 and IDC_STATIC_TRACK2 static controls.
- 78 -

Program the spin button control. The spin control depends on its buddy edit control, located immediately before
it in the tab order. Use ClassWizard to add a double-precision data member called m_dSpin for the
IDC_BUDDY_SPIN1 edit control. We're using a double instead of an int because the int would require almost
no programming, and that would be too easy. We want the edit control range to be 0.0 to 10.0, but the spin
control itself needs an integer range. Add the following code to OnInitDialog to set the spin control range to 0 to
100 and to set its initial value to m_dSpin * 10.0:
CSpinButtonCtrl* pSpin =
(CSpinButtonCtrl*) GetDlgItem(IDC_SPIN1);
pSpin->SetRange(0, 100);
pSpin->SetPos((int) (m_dSpin * 10.0));
To display the current value in the buddy edit control, you need to map the WM_VSCROLL message that the
spin control sends to the dialog. Here's the code:
void CEx06bDialog::OnVScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
if (nSBCode == SB_ENDSCROLL) {
return; // Reject spurious messages
}
// Process scroll messages from IDC_SPIN1 only
if (pScrollBar->GetDlgCtrlID() == IDC_SPIN1) {
CString strValue;
strValue.Format("%3.1f", (double) nPos / 10.0);
((CSpinButtonCtrl*) pScrollBar)->GetBuddy()
->SetWindowText(strValue);
}
}
There's no need for you to add code in OnOK or in DoDataExchange because the dialog data exchange code
processes the contents of the edit control. In the dialog editor, select the spin control's Auto Buddy property and
the buddy's Read-only property.
Set up an image list. Both the list control and the tree control need an image list, and the image list needs
icons.
First use the graphics editor to add icons to the project's RC file. On the companion CD-ROM, these icons are
circles with black outlines and different-colored interiors. Use fancier icons if you have them. You can import an
icon by choosing Resource from the Insert menu and then clicking the Import button. For this example, the icon
resource IDs are as follows.
Resource ID Icon File

IDI_BLACK Icon1

IDI_BLUE Icon3

IDI_CYAN Icon5

IDI_GREEN Icon7

IDI_PURPLE Icon6

IDI_RED Icon2

IDI_WHITE Icon0

IDI_YELLOW Icon4
Next add a private CImageList data member called m_imageList in the CEx06bDialog class header, and then
add the following code to OnInitDialog:
HICON hIcon[8];
int n;
m_imageList.Create(16, 16, 0, 8, 8); // 32, 32 for large icons
hIcon[0] = AfxGetApp()->LoadIcon(IDI_WHITE);
hIcon[1] = AfxGetApp()->LoadIcon(IDI_BLACK);
- 79 -

hIcon[2] = AfxGetApp()->LoadIcon(IDI_RED);
hIcon[3] = AfxGetApp()->LoadIcon(IDI_BLUE);
hIcon[4] = AfxGetApp()->LoadIcon(IDI_YELLOW);
hIcon[5] = AfxGetApp()->LoadIcon(IDI_CYAN);
hIcon[6] = AfxGetApp()->LoadIcon(IDI_PURPLE);
hIcon[7] = AfxGetApp()->LoadIcon(IDI_GREEN);
for (n = 0; n < 8; n++) {
m_imageList.Add(hIcon[n]);
}

About Icons
You probably know that a bitmap is an array of bits that represent pixels on the display. (You'll
learn more about bitmaps in Chapter 11.) In Windows, an icon is a "bundle" of bitmaps. First of
all, an icon has different bitmaps for different sizes. Typically, small icons are 16-by-16 pixels
and large icons are 32-by-32 pixels. Within each size are two separate bitmaps: one 4-bit-per-
pixel bitmap for the color image and one monochrome (1-bit-per-pixel) bitmap for the "mask." If
a mask bit is 0, the corresponding image pixel represents an opaque color. If the mask bit is 1,
an image color of black (0) means that the pixel is transparent and an image color of white
(0xF) means that the background color is inverted at the pixel location. Windows 95 and
Windows NT seem to process inverted colors a little differently than Windows 3.x does—the
inverted pixels show up transparent against the desktop, black against a Windows Explorer
window background, and white against list and tree control backgrounds. Don't ask me why.
Small icons were new with Windows 95. They're used in the task bar, in Windows Explorer,
and in your list and tree controls, if you want them there. If an icon doesn't have a 16-by-16-
pixel bitmap, Windows manufactures a small icon out of the 32-by-32-pixel bitmap, but it won't
be as neat as one you draw yourself.
The graphics editor lets you create and edit icons. Look at the color palette shown here.

The top square in the upper-left portion shows you the main color for brushes, shape interiors,
and so on, and the square under it shows the border color for shape outlines. You select a
main color by left-clicking on a color, and you select a border color by right-clicking on a color.
Now look at the top center portion of the color palette. You click on the upper "monitor" to paint
transparent pixels, which are drawn in dark cyan. You click on the lower monitor to paint
inverted pixels, which are drawn in red.
Program the list control. In the dialog editor, set the list control's style attributes as shown in the next illustration.

Make sure the Border style on the More Styles page is set. Next add the following code to OnInitDialog:
static char* color[] = {"white", "black", "red",
"blue", "yellow", "cyan",
"purple", "green"};
CListCtrl* pList =
- 80 -

(CListCtrl*) GetDlgItem(IDC_LISTVIEW1);
pList->SetImageList(&m_imageList, LVSIL_SMALL);
for (n = 0; n < 8; n++) {
pList->InsertItem(n, color[n], n);
}
pList->SetBkColor(RGB(0, 255, 255)); // UGLY!
pList->SetTextBkColor(RGB(0, 255, 255));
As the last two lines illustrate, you don't use the WM_CTLCOLOR message with common controls; you just call
a function to set the background color. As you'll see when you run the program, however, the icons' inverse-
color pixels look shabby.
If you use ClassWizard to map the list control's LVN_ITEMCHANGED notification message, you'll be able to
track the user's selection of items. The code in the following handler displays the selected item's text in a static
control:
void CEx06bDialog::OnItemchangedListview1(NMHDR* pNMHDR,
LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
CListCtrl* pList =
(CListCtrl*) GetDlgItem(IDC_LISTVIEW1);
int nSelected = pNMListView->iItem;
if (nSelected >= 0) {
CString strItem = pList->GetItemText(nSelected, 0);
SetDlgItemText(IDC_STATIC_LISTVIEW1, strItem);
}
*pResult = 0;
}
The NM_LISTVIEW structure has a data member called iItem that contains the index of the selected item.
Program the tree control. In the dialog editor, set the tree control's style attributes as shown here.

Next, add the following lines to OnInitDialog:


CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
pTree->SetImageList(&m_imageList, TVSIL_NORMAL);
// tree structure common values
TV_INSERTSTRUCT tvinsert;
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE |
TVIF_TEXT;
tvinsert.item.hItem = NULL;
tvinsert.item.state = 0;
tvinsert.item.stateMask = 0;
tvinsert.item.cchTextMax = 6;
tvinsert.item.iSelectedImage = 1;
tvinsert.item.cChildren = 0;
tvinsert.item.lParam = 0;
// top level
tvinsert.item.pszText = "Homer";
- 81 -

tvinsert.item.iImage = 2;
HTREEITEM hDad = pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Marge";
HTREEITEM hMom = pTree->InsertItem(&tvinsert);
// second level
tvinsert.hParent = hDad;
tvinsert.item.pszText = "Bart";
tvinsert.item.iImage = 3;
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Lisa";
pTree->InsertItem(&tvinsert);
// second level
tvinsert.hParent = hMom;
tvinsert.item.pszText = "Bart";
tvinsert.item.iImage = 4;
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Lisa";
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Dilbert";
HTREEITEM hOther = pTree->InsertItem(&tvinsert);
// third level
tvinsert.hParent = hOther;
tvinsert.item.pszText = "Dogbert";
tvinsert.item.iImage = 7;
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Ratbert";
pTree->InsertItem(&tvinsert);
As you can see, this code sets TV_INSERTSTRUCT text and image indexes and calls InsertItem to add nodes
to the tree.
Finally, use ClassWizard to map the TVN_SELCHANGED notification for the tree control. Here is the handler
code to display the selected text in a static control:
void CEx06bDialog::OnSelchangedTreeview1(NMHDR* pNMHDR,
LRESULT* pResult)
{
NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
HTREEITEM hSelected = pNMTreeView->itemNew.hItem;
if (hSelected != NULL) {
char text[31];
TV_ITEM item;
item.mask = TVIF_HANDLE | TVIF_TEXT;
item.hItem = hSelected;
item.pszText = text;
item.cchTextMax = 30;
VERIFY(pTree->GetItem(&item));
SetDlgItemText(IDC_STATIC_TREEVIEW1, text);
}
*pResult = 0;
}
The NM_TREEVIEW structure has a data member called itemNew that contains information about the selected
node; itemNew.hItem is the handle of that node. The GetItem function retrieves the node's data, storing the text
using a pointer supplied in the TV_ITEM structure. The mask variable tells Windows that the hItem handle is
valid going in and that text output is desired.
Add code to the virtual OnDraw function in file ex06bView.cpp. The following boldface code replaces the
previous code:
void CEx06bView::OnDraw(CDC* pDC)
- 82 -

{
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
Use ClassWizard to add the OnLButtonDown member function. Edit the AppWizard-generated code as follows:
void CEx06bView::OnLButtonDown(UINT nFlags, CPoint point)
{
CEx06bDialog dlg;

dlg.m_nTrackbar1 = 20;
dlg.m_nTrackbar2 = 2; // index for 8.0
dlg.m_nProgress = 70; // write-only
dlg.m_dSpin = 3.2;

dlg.DoModal();
}
Add a statement to include ex06bDialog.h in file ex06bView.cpp.
Compile and run the program. Experiment with the controls to see how they work. We haven't added code to
make the progress indicator functional; we'll cover that in Chapter 12.
45.1 Other Windows Common Controls
You've seen most of the common controls that appear on the dialog editor control palette. We've skipped the
animation control because this book doesn't cover multimedia, and we've skipped the hot key control because it isn't
very interesting. The tab control is interesting, but you seldom use it inside another dialog. Chapter 13 shows you
how to construct a tabbed dialog, sometimes known as a property sheet. In Chapter 13, you'll also see an application
that is built around the CRichEditView class, which incorporates the Windows rich edit control.
Chapter Seven
46. The Modeless Dialog and Windows Common Dialogs
In Chapter 6, you saw the ordinary modal dialog and most of the controls for Microsoft Windows. Now you'll move on
to the modeless dialog and to the common dialogs for Microsoft Windows 95 and Microsoft Windows NT versions 4.0
and later. Modeless dialogs, as you'll remember, allow the user to work elsewhere in the application while the dialog
is active. The common dialog classes are the C++ programming interface to the group of Windows utility dialogs that
include File Open, Page Setup, Color, and so forth and that are supported by the dynamic link library
COMDLG32.DLL.
In this chapter's first example, you'll build a simple modeless dialog that is controlled from a view. In the second
example, you'll derive from the COMDLG32 CFileDialog class a class that allows file deletion.
47. Modeless Dialogs
In the Microsoft Foundation Class (MFC) Library version 6.0, modal and modeless dialogs share the same base
class, CDialog, and they both use a dialog resource that you can build with the dialog editor. If you're using a
modeless dialog with a view, you'll need to know some specialized programming techniques.
47.1 Creating Modeless Dialogs
For modal dialogs, you've already learned that you construct a dialog object using a CDialog constructor that takes a
resource template ID as a parameter, and then you display the modal dialog window by calling the DoModal member
function. The window ceases to exist as soon as DoModal returns. Thus, you can construct a modal dialog object on
the stack, knowing that the dialog window has been destroyed by the time the C++ dialog object goes out of scope.
Modeless dialogs are more complicated. You start by invoking the CDialog default constructor to construct the dialog
object, but then to create the dialog window you need to call the CDialog::Create member function instead of
DoModal. Create takes the resource ID as a parameter and returns immediately with the dialog window still on the
screen. You must worry about exactly when to construct the dialog object, when to create the dialog window, when to
destroy the dialog, and when to process user-entered data.
Here's a summary of the differences between creating a modal dialog and a modeless dialog.
Modal Dialog Modeless Dialog

Constructor used Constructor with resource ID param Default constructor (no params)

Function used to create window DoModal Create with resource ID param


47.2 User-Defined Messages
Suppose you want the modeless dialog window to be destroyed when the user clicks the dialog's OK button. This
presents a problem. How does the view know that the user has clicked the OK button? The dialog could call a view
- 83 -

class member function directly, but that would "marry" the dialog to a particular view class. A better solution is for the
dialog to send the view a user-defined message as the result of a call to the OK button message-handling function.
When the view gets the message, it can destroy the dialog window (but not the object). This sets the stage for the
creation of a new dialog.
You have two options for sending Windows messages: the CWnd::SendMessage function or the PostMessage
function. The former causes an immediate call to the message-handling function, and the latter posts a message in
the Windows message queue. Because there's a slight delay with the PostMessage option, it's reasonable to expect
that the handler function has returned by the time the view gets the message.
47.3 Dialog Ownership
Now suppose you've accepted the dialog default pop-up style, which means that the dialog isn't confined to the
view's client area. As far as Windows is concerned, the dialog's "owner" is the application's main frame window
(introduced in Chapter 13), not the view. You need to know the dialog's view to send the view a message. Therefore,
your dialog class must track its own view through a data member that the constructor sets. The CDialog constructor's
pParent parameter doesn't have any effect here, so don't bother using it.
47.4 A Modeless Dialog Example—EX07A
We could convert the Chapter 6 monster dialog to a modeless dialog, but starting from scratch with a simpler dialog
is easier. Example EX07A uses a dialog with one edit control, an OK button, and a Cancel button. As in the Chapter
6 example, pressing the left mouse button while the mouse cursor is inside the view window brings up the dialog, but
now we have the option of destroying it in response to another event—pressing the right mouse button when the
mouse cursor is inside the view window. We'll allow only one open dialog at a time, so we must be sure that a
second left button press doesn't bring up a duplicate dialog.
To summarize the upcoming steps, the EX07A view class has a single associated dialog object that is constructed
on the heap when the view is constructed. The dialog window is created and destroyed in response to user actions,
but the dialog object is not destroyed until the application terminates.
Here are the steps to create the EX07A example:
Run AppWizard to produce \vcpp32\ex07a\ex07a. Accept all the defaults but two: select Single Document and
deselect Printing And Print Preview. The options and the default class names are shown here.

Use the dialog editor to create a dialog resource. Choose Resource from Visual C++'s Insert menu, and then
select Dialog. The dialog editor assigns the ID IDD_DIALOG1 to the new dialog. Change the dialog caption to
Modeless Dialog. Accept the default OK and Cancel buttons with IDs IDOK and IDCANCEL, and then add a
static text control and an edit control with the default ID IDC_EDIT1. Change the static text control's caption to
Edit 1. Here is the completed dialog.
- 84 -

Be sure to select the dialog's Visible property.


Use ClassWizard to create the CEx07aDialog class. Choose ClassWizard from Microsoft Visual C++'s View
menu. Fill in the New Class dialog as shown here, and then click the OK button.

Add the message-handling functions shown next. To add a message-handling function, click on an object
ID, click on a message, and then click the Add Function button. The Add Member Function dialog box
appears. Edit the function name if necessary, and click the OK button.
Object ID Message Member Function

IDCANCEL BN_CLICKED OnCancel

IDOK BN_CLICKED OnOK


Add a variable to the CEx07aDialog class. While in ClassWizard, click on the Member Variables tab, choose
the IDC_EDIT1 control, and then click the Add Variable button to add the CString variable m_strEdit1.
Edit ex07aDialog.h to add a view pointer and function prototypes. Type in the following boldface code in the
CEx07aDialog class declaration:
private:
CView* m_pView;
Also, add the function prototypes as follows:
public:
CEx07aDialog(CView* pView);
BOOL Create();
- 85 -

Using the CView class rather than the CEx07aView class allows the dialog class to be used with any
view class.
Edit ex07aDialog.h to define the WM_GOODBYE message ID. Add the following line of code:
#define WM_GOODBYE WM_USER + 5
The Windows constant WM_USER is the first message ID available for user-defined messages. The
application framework uses a few of these messages, so we'll skip over the first five messages.

Visual C++ maintains a list of symbol definitions in your project's resource.h file, but the resource
editor does not understand constants based on other constants. Don't manually add
WM_GOODBYE to resource.h because Visual C++ might delete it.
Add the modeless constructor in the file ex07aDialog.cpp. You could modify the existing CEx07aDialog
constructor, but if you add a separate one, the dialog class can serve for both modal and modeless dialogs.
Add the lines shown below.
CEx07aDialog::CEx07aDialog(CView* pView) // modeless constructor
{
m_pView = pView;
}
You should also add the following line to the AppWizard-generated modal constructor:
m_pView = NULL;
The C++ compiler is clever enough to distinguish between the modeless constructor CEx07aDialog(CView*)
and the modal constructor CEx07aDialog(CWnd*). If the compiler sees an argument of class CView or a
derived CView class, it generates a call to the modeless constructor. If it sees an argument of class CWnd or
another derived CWnd class, it generates a call to the modal constructor.
Add the Create function in ex07aDialog.cpp. This derived dialog class Create function calls the base class
function with the dialog resource ID as a parameter. Add the following lines:
BOOL CEx07aDialog::Create()
{
return CDialog::Create(CEx07aDialog::IDD);
}

Create is not a virtual function. You could have chosen a different name if you had wanted to.
Edit the OnOK and OnCancel functions in ex07aDialog.cpp. These virtual functions generated by ClassWizard
are called in response to dialog button clicks. Add the following boldface code:
void CEx07aDialog::OnCancel() // not really a message handler
{
if (m_pView != NULL) {
// modeless case -- do not call base class OnCancel
m_pView->PostMessage(WM_GOODBYE, IDCANCEL);
}
else {
CDialog::OnCancel(); // modal case
}
}

void CEx07aDialog::OnOK() // not really a message handler


{
if (m_pView != NULL) {
// modeless case -- do not call base class OnOK
UpdateData(TRUE);
m_pView->PostMessage(WM_GOODBYE, IDOK);
}
else {
CDialog::OnOK(); // modal case
}
}
- 86 -

If the dialog is being used as a modeless dialog, it sends the user-defined message WM_GOODBYE to the
view. We'll worry about handling the message later.

For a modeless dialog, be sure you do not call the CDialog::OnOK or CDialog::OnCancel function.
This means you must override these virtual functions in your derived class; otherwise, using the Esc
key, the Enter key, or a button click would result in a call to the base class functions, which call the
Windows EndDialog function. EndDialog is appropriate only for modal dialogs. In a modeless dialog,
you must call DestroyWindow instead, and if necessary, you must call UpdateData to transfer data
from the dialog controls to the class data members.
Edit the ex07aView.h header file. You need a data member to hold the dialog pointer:
private:
CEx07aDialog* m_pDlg;
If you add the forward declaration
class CEx07aDialog;
at the beginning of ex07aView.h, you won't have to include ex07aDialog.h in every module that includes
ex07aView.h.
Modify the CEx07aView constructor and destructor in the file ex07aView.cpp. The CEx07aView class has a
data member m_pDlg that points to the view's CEx07aDialog object. The view constructor constructs the dialog
object on the heap, and the view destructor deletes it. Add the following boldface code:
CEx07aView::CEx07aView()
{
m_pDlg = new CEx07aDialog(this);
}

CEx07aView::~CEx07aView()
{
delete m_pDlg; // destroys window if not already destroyed
}
Add code to the virtual OnDraw function in the ex07aView.cpp file. The CEx07aView OnDraw function (whose
skeleton was generated by AppWizard) should be coded as follows in order to prompt the user to press the
mouse button:
void CEx07aView::OnDraw(CDC* pDC)
{
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
Use ClassWizard to add CEx07aView mouse message handlers. Add handlers for the WM_LBUTTONDOWN
and WM_RBUTTONDOWN messages. Now edit the code in file ex07aView.cpp as follows:
void CEx07aView::OnLButtonDown(UINT nFlags, CPoint point)
{
// creates the dialog if not created already
if (m_pDlg->GetSafeHwnd() == 0) {
m_pDlg->Create(); // displays the dialog window
}
}

void CEx07aView::OnRButtonDown(UINT nFlags, CPoint point)


{
m_pDlg->DestroyWindow();
// no problem if window was already destroyed
}
For most window types except main frame windows, the DestroyWindow function does not destroy the C++
object. We want this behavior because we'll take care of the dialog object's destruction in the view destructor.
Add the dialog header include statement to file ex07aView.cpp. While you're in ex07aView.cpp, add the
following dialog header include statement after the view header include statement:
#include "ex07aView.h"
#include "ex07aDialog.h"
- 87 -

Add your own message code for the WM_GOODBYE message. Because ClassWizard does not support user-
defined messages, you must write the code yourself. This task makes you appreciate the work ClassWizard
does for the other messages.
In ex07aView.cpp, add the following line after the BEGIN_MESSAGE_MAP statement but outside the
AFX_MSG_MAP brackets:
ON_MESSAGE(WM_GOODBYE, OnGoodbye)
Also in ex07aView.cpp, add the message handler function itself:
LRESULT CEx07aView::OnGoodbye(WPARAM wParam, LPARAM lParam)
{
// message received in response to modeless dialog OK
// and Cancel buttons
TRACE("CEx07aView::OnGoodbye %x, %lx\n", wParam, lParam);
TRACE("Dialog edit1 contents = %s\n",
(const char*) m_pDlg->m_strEdit1);
m_pDlg->DestroyWindow();
return 0L;
}
In ex07aView.h, add the following function prototype before the DECLARE_MESSAGE_MAP() statement but
outside the AFX_ MSG brackets:
afx_msg LRESULT OnGoodbye(WPARAM wParam, LPARAM lParam);
With Win32, the wParam and lParam parameters are the usual means of passing message data. In a mouse
button down message, for example, the mouse x and y coordinates are packed into the lParam value. With the
MFC library, message data is passed in more meaningful parameters. The mouse position is passed as a
CPoint object. User-defined messages must use wParam and lParam, so you can use these two variables
however you want. In this example, we've put the button ID in wParam.
Build and test the application. Build and run EX07A. Press the left mouse button, and then press the right
button. (Be sure the mouse cursor is outside the dialog window when you press the right mouse button.) Press
the left mouse button again and enter some data, and then click the dialog's OK button. Does the view's
TRACE statement correctly list the edit control's contents?

If you use the EX07A view and dialog classes in an MDI application, each MDI child window can
have one modeless dialog. When the user closes an MDI child window, the child's modeless dialog
is destroyed because the view's destructor calls the dialog destructor, which, in turn, destroys the
dialog window.
48. The CFormView Class—A Modeless Dialog Alternative
If you need an application based on a single modeless dialog, the CFormView class will save you a lot of work. You'll
have to wait until Chapter 16, however, because the CFormView class is most useful when coupled with the
CDocument class, and we haven't progressed that far in our exploration of the application framework.
49. The Windows Common Dialogs
Windows provides a group of standard user interface dialogs, and these are supported by the MFC library classes.
You are probably familiar with all or most of these dialogs because so many Windows-based applications, including
Visual C++, already use them. All the common dialog classes are derived from a common base class,
CCommonDialog. A list of the COMDLG32 classes is shown in the following table.
Class Purpose

CColorDialog Allows the user to select or create a color

CFileDialog Allows the user to open or save a file

CFindReplaceDialog Allows the user to substitute one string for another

CPageSetupDialog Allows the user to input page measurement parameters

CFontDialog Allows the user to select a font from a list of available fonts

CPrintDialog Allows the user to set up the printer and print a document
Here's one characteristic that all common dialogs share: they gather information from the user, but they don't do
anything with it. The file dialog can help the user select a file to open, but it really just provides your program with the
- 88 -

pathname—your program must make the call that opens the file. Similarly, a font dialog fills in a structure that
describes a font, but it doesn't create the font.
49.1 Using the CFileDialog Class Directly
Using the CFileDialog class to open a file is easy. The following code opens a file that the user has selected through
the dialog:
CFileDialog dlg(TRUE, "bmp", "*.bmp");
if (dlg.DoModal() == IDOK) {
CFile file;
VERIFY(file.Open(dlg.GetPathName(), CFile::modeRead));
}
The first constructor parameter (TRUE) specifies that this object is a "File Open" dialog instead of a "File Save"
dialog. The default file extension is bmp, and *.bmp appears first in the filename edit box. The
CFileDialog::GetPathName function returns a CString object that contains the full pathname of the selected file.
49.2 Deriving from the Common Dialog Classes
Most of the time, you can use the common dialog classes directly. If you derive your own classes, you can add
functionality without duplicating code. Each COMDLG32 dialog works a little differently, however. The next example
is specific to the file dialog, but it should give you some ideas for customizing the other common dialogs.

In the early editions of this book, the EX07B example dynamically created controls inside the
standard file dialog. That technique doesn't work in Win32, but the nested dialog method described
here has the same effect.
49.3 Nested Dialogs
Win32 provides a way to "nest" one dialog inside another so that multiple dialogs appear as one seamless whole.
You must first create a dialog resource template with a "hole" in it—typically a group box control—with the specific
child window ID stc32 (=0x045f). Your program sets some parameters that tell COMDLG32 to use your template. In
addition, your program must hook into the COMDLG32 message loop so that it gets first crack at selected
notifications. When you're done with all of this, you'll notice that you have created a dialog window that is a child of
the COMDLG32 dialog window, even though your template wraps COMDLG32's template.
This sounds difficult, and it is unless you use MFC. With MFC, you build the dialog resource template as described
above, derive a class from one of the common dialog base classes, add the class-specific connection code in
OnInitDialog, and then happily use ClassWizard to map the messages that originate from your template's new
controls.

Windows NT 3.51 uses an earlier version of the common dialogs DLL that does not support the new
Windows namespace feature. The nested dialog technique illustrated in the EX07B example won't
work with the Windows NT 3.51 version of the file dialog.
49.4 A CFileDialog Example—EX07B
In this example, you will derive a class CEx07bDialog that adds a working Delete All Matching Files button to the
standard file dialog. It also changes the dialog's title and changes the Open button's caption to Delete (to delete a
single file). The example illustrates how you can use nested dialogs to add new controls to standard common
dialogs. The new file dialog is activated as in the previous examples—by pressing the left mouse button when the
mouse cursor is in the view window. Because you should be gaining skill with Visual C++, the following steps won't
be as detailed as those for the earlier examples. Figure 7-1 shows what the dialog looks like.
- 89 -

Figure 7-1. The Delete File dialog in action.


Follow these steps to build the EX07B application:
Run AppWizard to produce \vcpp32\ex07b\ex07b. Accept all the defaults but two: select Single Document and
deselect Printing And Print Preview. The options and the default class names are shown in the next graphic.

Use the dialog editor to create a dialog resource. Make the dialog box about 3-by-5 inches, and use the ID
IDD_FILESPECIAL. Set the dialog's Style property to Child, its Border property to None, and select its Clip
Siblings and Visible properties. Create the template with a button with ID IDC_DELETE and a group box with ID
stc32=0x045f, as shown here.
- 90 -

Check your work by choosing Resource Symbols from the Visual C++ View menu. You should see a
symbol list like the one shown in the graphic below.

Use ClassWizard to create the CSpecialFileDialog class. Fill in the New Class dialog, as shown here, and then
click the Change button.
- 91 -

Change the names to SpecFileDlg.h and SpecFileDlg.cpp. Unfortunately, we cannot use the Base Class
drop-down list to change the base class to CFileDialog, as that would decouple our class from the
IDD_FILESPECIAL template. We have to change the base class by hand.
Edit the file SpecFileDlg.h. Change the line
class CSpecialFileDialog : public CDialog
to
class CSpecialFileDialog : public CFileDialog
Add the following two public data members:
CString m_strFilename;
BOOL m_bDeleteAll;
Finally, edit the constructor declaration:
CSpecialFileDialog(BOOL bOpenFileDialog,
LPCTSTR lpszDefExt = NULL,
LPCTSTR lpszFileName = NULL,
DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
LPCTSTR lpszFilter = NULL,
CWnd* pParentWnd = NULL
);
Replace CDialog with CFileDialog in SpecFileDlg.h. Choose Replace from Visual C++'s Edit menu, and replace
this name globally.
Edit the CSpecialFileDialog constructor in SpecFileDlg.cpp. The derived class destructor must invoke the base
class constructor and initialize the m_bDeleteAll data member. In addition, it must set some members of the
CFileDialog base class data member m_ofn, which is an instance of the Win32 OPENFILENAME structure.
The Flags and lpTemplateName members control the coupling to your IDD_FILESPECIAL template, and the
lpstrTitle member changes the main dialog box title. Edit the constructor as follows:
CSpecialFileDialog::CSpecialFileDialog(BOOL bOpenFileDialog,
LPCTSTR lpszDefExt, LPCTSTR lpszFileName, DWORD dwFlags,
LPCTSTR lpszFilter, CWnd* pParentWnd)
: CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,
dwFlags, lpszFilter, pParentWnd)
{
//{{AFX_DATA_INIT(CSpecialFileDialog)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
- 92 -

m_ofn.Flags |= OFN_ENABLETEMPLATE;
m_ofn.lpTemplateName = MAKEINTRESOURCE(IDD_FILESPECIAL);
m_ofn.lpstrTitle = "Delete File";
m_bDeleteAll = FALSE;
}
Map the WM_INITDIALOG message in the CSpecialDialog class. The OnInitDialog member function needs to
change the common dialog's Open button caption to Delete. The child window ID is IDOK.
BOOL bRet = CFileDialog::OnInitDialog();
if (bRet == TRUE) {
GetParent()->GetDlgItem(IDOK)->SetWindowText("Delete");
}
return bRet;
Map the new IDC_DELETE button (Delete All Matching Files) in the CSpecialDialog class. The OnDelete
member function sets the m_bDeleteAll flag and then forces the main dialog to exit as if the Cancel button had
been clicked. The client program (in this case, the view) gets the IDCANCEL return from DoModal and reads
the flag to see whether it should delete all files. Here is the function:
void CSpecialFileDialog::OnDelete()
{
m_bDeleteAll = TRUE;
// 0x480 is the child window ID of the File Name edit control
// (as determined by SPYXX)
GetParent()->GetDlgItem(0x480)->GetWindowText(m_strFilename);
GetParent()->SendMessage(WM_COMMAND, IDCANCEL);
}
Add code to the virtual OnDraw function in file ex07bView.cpp. The CEx07bView OnDraw function (whose
skeleton was generated by AppWizard) should be coded as follows to prompt the user to press the mouse
button:
void CEx07bView::OnDraw(CDC* pDC)
{
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
Add the OnLButtonDown message handler to the CEx07bView class. Use ClassWizard to create the message
handler for WM_LBUTTON-DOWN, and then edit the code as follows:
void CEx07bView::OnLButtonDown(UINT nFlags, CPoint point)
{
CSpecialFileDialog dlgFile(TRUE, NULL, "*.obj");
CString strMessage;
int nModal = dlgFile.DoModal();
if ((nModal == IDCANCEL) && (dlgFile.m_bDeleteAll)) {
strMessage.Format(
"Are you sure you want to delete all %s files?",
dlgFile.m_strFilename);
if (AfxMessageBox(strMessage, MB_YESNO) == IDYES) {
HANDLE h;
WIN32_FIND_DATA fData;
while((h = ::FindFirstFile(dlgFile.m_strFilename, &fData))
!= (HANDLE) 0xFFFFFFFF) { // no MFC equivalent
if (::DeleteFile(fData.cFileName) == FALSE) {
strMessage.Format("Unable to delete file %s\n",
fData.cFileName);
AfxMessageBox(strMessage);
break;
}
}
}
}
- 93 -

else if (nModal == IDOK) {


CString strSingleFilename = dlgFile.GetPathName();
strMessage.Format(
"Are you sure you want to delete %s?", strSingleFilename);
if (AfxMessageBox(strMessage, MB_YESNO) == IDYES) {
CFile::Remove(strSingleFilename);
}
}
}
Remember that common dialogs just gather data. Since the view is the client of the dialog, the view must call
DoModal for the file dialog object and then figure out what to do with the information returned. In this case, the
view has the return value from DoModal (either IDOK or IDCANCEL) and the value of the public m_bDeleteAll
data member, and it can call various CFileDialog member functions such as GetPathName. If DoModal returns
IDCANCEL and the flag is TRUE, the function makes the Win32 file system calls necessary to delete all the
matching files. If DoModal returns IDOK, the function can use the MFC CFile functions to delete an individual
file.
Using the global AfxMessageBox function is a convenient way to pop up a simple dialog that displays some text
and then queries the user for a Yes/No answer. The Microsoft Foundation Classes And Templates section in
the online help describes all of the message box variations and options.
Of course, you'll need to include the statement
#include "SpecFileDlg.h"
after the line
#include "ex07bView.h"
Build and test the application.Build and run EX07B. Pressing the left mouse button should bring up the Delete
File dialog, and you should be able to use it to navigate through the disk directory and to delete files. Be careful
not to delete your important source files!
49.5 Other Customization for CFileDialog
In the EX07B example, you added a pushbutton to the dialog. It's easy to add other controls too. Just put them in the
resource template, and if they are standard Windows controls such as edit controls or list boxes, you can use
ClassWizard to add data members and DDX/DDV code to your derived class. The client program can set the data
members before calling DoModal, and it can retrieve the updated values after DoModal returns.

Even if you don't use nested dialogs, two windows are still associated with a CFileDialog object.
Suppose you have overridden OnInitDialog in a derived class and you want to assign an icon to the
file dialog. You must call CWnd::GetParent to get the top-level window, just as you did in the EX07B
example. Here's the code:
HICON hIcon = AfxGetApp()->LoadIcon(ID_MYICON);
GetParent()->SetIcon(hIcon, TRUE); // Set big icon
GetParent()->SetIcon(hIcon, FALSE); // Set small icon
Chapter Eight
50. Using ActiveX Controls
Microsoft Visual Basic (VB) was introduced in 1991 and has proven to be a wildly popular and successful application
development system for Microsoft Windows. Part of its success is attributable to its open-ended nature. The 16-bit
versions of VB (versions 1 through 3) supported Visual Basic controls (VBXs), ready-to-run software components
that VB developers could buy or write themselves. VBXs became the center of a whole industry, and pretty soon
there were hundreds of them. At Microsoft, the Microsoft Foundation Class (MFC) team figured out a way for
Microsoft Visual C++ programmers to use VBXs in their programs, too.
The VBX standard, which was highly dependent on the 16-bit segment architecture, did not make it to the 32-bit
world. Now ActiveX Controls (formerly known as OLE controls, or OCXs) are the industrial-strength replacement for
VBXs based on Microsoft COM technology. ActiveX controls can be used by application developers in both VB and
Visual C++ 6.0. While VBXs were written mostly in plain C, ActiveX controls can be written in C++ with the help of
the MFC library or with the help of the ActiveX Template Library (ATL).
This chapter is not about writing ActiveX controls; it's about using them in a Visual C++ application. The premise
here is that you can learn to use ActiveX controls without knowing much about the Component Object Model (COM)
on which they're based. After all, Microsoft doesn't require that VB programmers be COM experts. To effectively
write ActiveX controls, however, you need to know a bit more, starting with the fundamentals of COM. Consider
picking up a copy of Adam Denning's ActiveX Controls Inside Out (Microsoft Press, 1997) if you're serious about
- 94 -

creating ActiveX controls. Of course, knowing more ActiveX Control theory won't hurt when you're using the controls
in your programs. Chapter 24, Chapter 25, and Chapter 30 of this book are a good place to start.
51. ActiveX Controls vs. Ordinary Windows Controls
An ActiveX control is a software module that plugs into your C++ program the same way a Windows control does. At
least that's the way it seems at first. It's worthwhile here to analyze the similarities and differences between ActiveX
controls and the controls you already know.
51.1 Ordinary Controls—A Frame of Reference
In Chapter 6, you used ordinary Windows controls such as the edit control and the list box, and you saw the
Windows common controls that work in much the same way. These controls are all child windows that you use most
often in dialogs, and they are represented by MFC classes such as CEdit and CTreeCtrl. The client program is
always responsible for the creation of the control's child window.
Ordinary controls send notification command messages (standard Windows messages), such as BN_CLICKED, to
the dialog. If you want to perform an action on the control, you call a C++ control class member function, which
sends a Windows message to the control. The controls are all windows in their own right. All the MFC control classes
are derived from CWnd, so if you want to get the text from an edit control, you call CWnd::GetWindowText. But even
that function works by sending a message to the control.
Windows controls are an integral part of Windows, even though the Windows common controls are in a separate
DLL. Another species of ordinary control, the so-called custom control, is a programmer-created control that acts as
an ordinary control in that it sends WM_COMMAND notifications to its parent window and receives user-defined
messages. You'll see one of these in Chapter 22.
51.2 How ActiveX Controls Are Similar to Ordinary Controls
You can consider an ActiveX control to be a child window, just as an ordinary control is. If you want to include an
ActiveX control in a dialog, you use the dialog editor to place it there, and the identifier for the control turns up in the
resource template. If you're creating an ActiveX control on the fly, you call a Create member function for a class that
represents the control, usually in the WM_CREATE handler for the parent window. When you want to manipulate an
ActiveX control, you call a C++ member function, just as you do for a Windows control. The window that contains a
control is called a container.
51.3 How ActiveX Controls Are Different from Ordinary Controls—Properties and Methods
The most prominent ActiveX Controls features are properties and methods. Those C++ member functions that you
call to manipulate a control instance all revolve around properties and methods. Properties have symbolic names
that are matched to integer indexes. For each property, the control designer assigns a property name, such as
BackColor or GridCellEffect, and a property type, such as string, integer, or double. There's even a picture type for
bitmaps and icons. The client program can set an individual ActiveX control property by specifying the property's
integer index and its value. The client can get a property by specifying the index and accepting the appropriate return
value. In certain cases, ClassWizard lets you define data members in your client window class that are associated
with the properties of the controls the client class contains. The generated Dialog Data Exchange (DDX) code
exchanges data between the control properties and the client class data members.
ActiveX Controls methods are like functions. A method has a symbolic name, a set of parameters, and a return
value. You call a method by calling a C++ member function of the class that represents the control. A control
designer can define any needed methods, such as PreviousYear, LowerControlRods, and so forth.
An ActiveX control doesn't send WM_ notification messages to its container the way ordinary controls do; instead, it
"fires events." An event has a symbolic name and can have an arbitrary sequence of parameters—it's really a
container function that the control calls. Like ordinary control notification messages, events don't return a value to the
ActiveX control. Examples of events are Click, KeyDown, and NewMonth. Events are mapped in your client class just
as control notification messages are.
In the MFC world, ActiveX controls act just like child windows, but there's a significant layer of code between the
container window and the control window. In fact, the control might not even have a window. When you call Create,
the control's window isn't created directly; instead, the control code is loaded and given the command for "in-place
activation." The ActiveX control then creates its own window, which MFC lets you access through a CWnd pointer.
It's not a good idea for the client to use the control's hWnd directly, however.
A DLL is used to store one or more ActiveX controls, but the DLL often has an OCX filename extension instead of a
DLL extension. Your container program loads the DLLs when it needs them, using sophisticated COM techniques
that rely on the Windows Registry. For the time being, simply accept the fact that once you specify an ActiveX control
at design time, it will be loaded for you at runtime. Obviously, when you ship a program that requires special ActiveX
controls, you'll have to include the OCX files and an appropriate setup program.
52. Installing ActiveX Controls
Let's assume you've found a nifty ActiveX control that you want to use in your project. Your first step is to copy the
control's DLL to your hard disk. You could put it anywhere, but it's easier to track your ActiveX controls if you put
them in one place, such as in the system directory (typically \Windows\System for Microsoft Windows 95 or
- 95 -

\Winnt\System32 for Microsoft Windows NT). Copy associated files such as help (HLP) or license (LIC) files to the
same directory.
Your next step is to register the control in the Windows Registry. Actually, the ActiveX control registers itself when a
client program calls a special exported function. The Windows utility Regsvr32 is a client that accepts the control
name on the command line. Regsvr32 is suitable for installation scripts, but another program, RegComp, in the
project REGCOMP on the companion CD-ROM for this book, lets you find your control by browsing the disk. Some
controls have licensing requirements, which might involve extra entries to the Registry. (See Chapter 15, Chapter 17,
Chapter 24, and Chapter 25 for information about how the Windows Registry works.) Licensed controls usually come
with setup programs that take care of those details.
After you register your ActiveX control, you must install it in each project that uses it. That doesn't mean that the
OCX file gets copied. It means that ClassWizard generates a copy of a C++ class that's specific to the control, and it
means that the control shows up in the dialog editor control palette for that project.
To install an ActiveX control in a project, choose Add To Project from the Project menu and then choose
Components And Controls. Select Registered ActiveX Controls, as shown in the following illustration.

This gets you the list of all the ActiveX controls currently registered on your system. A typical list is shown here.
- 96 -

53. The Calendar Control


The MSCal.ocx control is a popular Microsoft ActiveX Calendar control that's probably already installed and
registered on your computer. If it isn't there, don't worry. It's on the CD-ROM that comes with this book.
Figure 8-1 shows the Calendar control inside a modal dialog.

Figure 8-1. The Calendar control in use.


The Calendar control comes with a help file that lists the control's properties, methods, and events shown here.
Properties Methods Events

BackColor AboutBox AfterUpdate

Day NextDay BeforeUpdate

DayFont NextMonth Click

DayFontColor NextWeek DblClick

DayLength NextYear KeyDown

FirstDay PreviousDay KeyPress


- 97 -

GridCellEffect PreviousMonth KeyUp

GridFont PreviousWeek NewMonth

GridFontColor PreviousYear NewYear

GridLinesColor Refresh

Month Today

MonthLength

ShowDateSelectors

ShowDays

ShowHorizontalGridlines

ShowTitle

ShowVerticalGridlines

TitleFont

TitleFontColor
Value

ValueIsNull

Year
You'll be using the BackColor, Day, Month, Year, and Value properties in the EX08A example later in this chapter.
BackColor is an unsigned long, but it is used as an OLE_COLOR, which is almost the same as a COLORREF. Day,
Month, and Year are short integers. Value's type is the special type VARIANT, which is described in Chapter 25. It
holds the entire date as a 64-bit value.
Each of the properties, methods, and events listed above has a corresponding integer identifier. Information about
the names, types, parameter sequences, and integer IDs is stored inside the control and is accessible to
ClassWizard at container design time.
54. ActiveX Control Container Programming
MFC and ClassWizard support ActiveX controls both in dialogs and as "child windows." To use ActiveX controls, you
must understand how a control grants access to properties, and you must understand the interactions between your
DDX code and those property values.
54.1 Property Access
The ActiveX control developer designates certain properties for access at design time. Those properties are
specified in the property pages that the control displays in the dialog editor when you right-click on a control and
choose Properties. The Calendar control's main property page looks like the one shown next.
- 98 -

When you click on the All tab, you will see a list of all the design- time-accessible properties, which might include a
few properties not found on the Control tab. The Calendar control's All page looks like this.

All the control's properties, including the design-time properties, are accessible at runtime. Some properties,
however, might be designated as read-only.
54.2 ClassWizard's C++ Wrapper Classes for ActiveX Controls
When you insert an ActiveX control into a project, ClassWizard generates a C++ wrapper class, derived from CWnd,
that is tailored to your control's methods and properties. The class has member functions for all properties and
methods, and it has constructors that you can use to dynamically create an instance of the control. (ClassWizard
also generates wrapper classes for objects used by the control.) Following are a few typical member functions from
the file Calendar.cpp that ClassWizard generates for the Calendar control:
unsigned long CCalendar::GetBackColor()
{
unsigned long result;
InvokeHelper(DISPID_BACKCOLOR, DISPATCH_PROPERTYGET,
VT_I4, (void*)&result, NULL);
return result;
}

void CCalendar::SetBackColor(unsigned long newValue)


{
static BYTE parms[] =
VTS_I4;
InvokeHelper(DISPID_BACKCOLOR, DISPATCH_PROPERTYPUT,
VT_EMPTY, NULL, parms, newValue);
}

short CCalendar::GetDay()
{
short result;
InvokeHelper(0x11, DISPATCH_PROPERTYGET, VT_I2,
(void*)&result, NULL);
return result;
}

void CCalendar::SetDay(short nNewValue)


{
static BYTE parms[] =
VTS_I2;
InvokeHelper(0x11, DISPATCH_PROPERTYPUT, VT_EMPTY,
NULL, parms, nNewValue);
}
- 99 -

COleFont CCalendar::GetDayFont()
{
LPDISPATCH pDispatch;
InvokeHelper(0x1, DISPATCH_PROPERTYGET, VT_DISPATCH,
(void*)&pDispatch, NULL);
return COleFont(pDispatch);
}

void CCalendar::SetDayFont(LPDISPATCH newValue)


{
static BYTE parms[] =
VTS_DISPATCH;
InvokeHelper(0x1, DISPATCH_PROPERTYPUT, VT_EMPTY,
NULL, parms, newValue);
}

VARIANT CCalendar::GetValue()
{
VARIANT result;
InvokeHelper(0xc, DISPATCH_PROPERTYGET, VT_VARIANT,
(void*)&result, NULL);
return result;
}

void CCalendar::SetValue(const VARIANT& newValue)


{
static BYTE parms[] =
VTS_VARIANT;
InvokeHelper(0xc, DISPATCH_PROPERTYPUT, VT_EMPTY,
NULL, parms, &newValue);
}

void CCalendar::NextDay()
{
InvokeHelper(0x16, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}

void CCalendar::NextMonth()
{
InvokeHelper(0x17, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
You don't have to concern yourself too much with the code inside these functions, but you can match up the first
parameter of each InvokeHelper function with the dispatch ID for the corresponding property or method in the
Calendar control property list. As you can see, properties always have separate Set and Get functions. To call a
method, you simply call the corresponding function. For example, to call the NextDay method from a dialog class
function, you write code such as this:
m_calendar.NextDay();
In this case, m_calendar is an object of class CCalendar, the wrapper class for the Calendar control.
54.3 AppWizard Support for ActiveX Controls
When the AppWizard ActiveX Controls option is checked (the default), AppWizard inserts the following line in your
application class InitInstance member function:
AfxEnableControlContainer();
It also inserts the following line in the project's StdAfx.h file:
#include <afxdisp.h>
- 100 -

If you decide to add ActiveX controls to an existing project that doesn't include the two lines above, you can simply
add the lines.
54.4 ClassWizard and the Container Dialog
Once you've used the dialog editor to generate a dialog template, you already know that you can use ClassWizard to
generate a C++ class for the dialog window. If your template contains one or more ActiveX controls, you can use
ClassWizard to add data members and event handler functions.
54.4.1 Dialog Class Data Members vs. Wrapper Class Usage
What kind of data members can you add to the dialog for an ActiveX control? If you want to set a control property
before you call DoModal for the dialog, you can add a dialog data member for that property. If you want to change
properties inside the dialog member functions, you must take another approach: you add a data member that is an
object of the wrapper class for the ActiveX control.
Now is a good time to review the MFC DDX logic. Look back at the Cincinnati dialog in Chapter 6. The
CDialog::OnInitDialog function calls CWnd::UpdateData(FALSE) to read the dialog class data members, and the
CDialog::OnOK function calls UpdateData(TRUE) to write the members. Suppose you added a data member for
each ActiveX control property and you needed to get the Value property value in a button handler. If you called
UpdateData(FALSE) in the button handler, it would read all the property values from all the dialog's controls—clearly
a waste of time. It's more effective to avoid using a data member and to call the wrapper class Get function instead.
To call that function, you must first tell ClassWizard to add a wrapper class object data member.
Suppose you have a Calendar wrapper class CCalendar and you have an m_calendar data member in your dialog
class. If you want to get the Value property, you do it like this:
COleVariant var = m_calendar.GetValue();

The VARIANT type and COleVariant class are described in Chapter 25.
Now consider another case: you want to set the day to the 5th of the month before the control is displayed. To do this
by hand, add a dialog class data member m_sCalDay that corresponds to the control's short integer Day property.
Then add the following line to the DoDataExchange function:
DDX_OCShort(pDX, ID_CALENDAR1, 0x11, m_sCalDay);
The third parameter is the Day property's integer index (its DispID), which you can find in the GetDay and SetDay
functions generated by ClassWizard for the control. Here's how you construct and display the dialog:
CMyDialog dlg;
dlg.m_sCalDay = 5;
dlg.DoModal();
The DDX code takes care of setting the property value from the data member before the control is displayed. No
other programming is needed. As you would expect, the DDX code sets the data member from the property value
when the user clicks the OK button.

Even when ClassWizard correctly detects a control's properties, it can't always generate data
members for all of them. In particular, no DDX functions exist for VARIANT properties like the
Calendar's Value property. You'll have to use the wrapper class for these properties.
54.4.2 Mapping ActiveX Control Events
ClassWizard lets you map ActiveX control events the same way you map Windows messages and command
messages from controls. If a dialog class contains one or more ActiveX controls, ClassWizard adds and maintains an
event sink map that connects mapped events to their handler functions. It works something like a message map. You
can see the code in Figure 8-2.

ActiveX controls have the annoying habit of firing events before your program is ready for them. If
your event handler uses windows or pointers to C++ objects, it should verify the validity of those
entities prior to using them.
54.5 Locking ActiveX Controls in Memory
Normally, an ActiveX control remains mapped in your process as long as its parent dialog is active. That means it
must be reloaded each time the user opens a modal dialog. The reloads are usually quicker than the initial load
because of disk caching, but you can lock the control into memory for better performance. To do so, add the
following line in the overridden OnInitDialog function after the base class call:
AfxOleLockControl(m_calendar.GetClsid());
The ActiveX control remains mapped until your program exits or until you call the AfxOleUnlockControl function.
- 101 -

55. The EX08A Example—An ActiveX Control Dialog Container


Now it's time to build an application that uses a Calendar control in a dialog. Here are the steps to create the EX08A
example:
Verify that the Calendar control is registered. If the control does not appear in the Visual C++ Gallery's
Registered ActiveX Controls page, copy the files MSCal.ocx, MSCal.hlp, and MSCal.cnt to your system
directory and register the control by running the REGCOMP program.
Run AppWizard to produce \vcpp32\ex08a\ex08a. Accept all of the default settings but two: select Single
Document and deselect Printing And Print Preview. In the AppWizard Step 3 dialog, make sure the ActiveX
Controls option is selected, as shown below.

Install the Calendar control in the EX08A project. Choose Add To Project from Visual C++'s Project menu, and
then choose Components And Controls. Choose Registered ActiveX Controls, and then choose Calendar
Control 8.0. ClassWizard generates two classes in the EX08A directory, as shown here.

Edit the Calendar control class to handle help messages. Add Calendar.cpp to the following message map
code:
BEGIN_MESSAGE_MAP(CCalendar, CWnd)
- 102 -

ON_WM_HELPINFO()
END_MESSAGE_MAP()
In the same file, add the OnHelpInfo function:
BOOL CCalendar::OnHelpInfo(HELPINFO* pHelpInfo)
{
// Edit the following string for your system
::WinHelp(GetSafeHwnd(), "c:\\winnt\\system32\\mscal.hlp",
HELP_FINDER, 0);
return FALSE;
}
In Calendar.h, add the function prototype and declare the message map:
protected:
afx_msg BOOL OnHelpInfo(HELPINFO* pHelpInfo);
DECLARE_MESSAGE_MAP()
The OnHelpInfo function is called if the user presses the F1 key when the Calendar control has the input focus.
We have to add the message map code by hand because ClassWizard doesn't modify generated ActiveX
classes.

The ON_WM_HELPINFO macro maps the WM_HELP message, which is new to Microsoft
Windows 95 and Microsoft Windows NT 4.0. You can use ON_WM_HELPINFO in any view or
dialog class and then code the handler to activate any help system. Chapter 21 describes the
MFC context-sensitive help system, some of which predates the WM_HELP message.
Use the dialog editor to create a new dialog resource. Choose Resource from Visual C++'s Insert menu, and
then choose Dialog. The dialog editor assigns the ID IDD_DIALOG1 to the new dialog. Next change the ID to
IDD_ACTIVEXDIALOG, change the dialog caption to ActiveX Dialog, and set the dialog's Context Help property
(on the More Styles page). Accept the default OK and Cancel buttons with the IDs IDOK and IDCANCEL, and
then add the other controls as shown in Figure 8-1. Make the Select Date button the default button. Drag the
Calendar control from the control palette. Then set an appropriate tab order. Assign control IDs as shown in the
following table.
Control ID

Calendar control IDC_CALENDAR1

Select Date button IDC_SELECTDATE

Edit control IDC_DAY


Edit control IDC_MONTH

Edit control IDC_YEAR

Next Week button IDC_NEXTWEEK


Use ClassWizard to create the CActiveXDialog class. If you run ClassWizard directly from the dialog editor
window, it will know that you want to create a CDialog-derived class based on the IDD_ACTIVEXDIALOG
template. Simply accept the default options, and name the class CActiveXDialog.
Click on the ClassWizard Message Maps tab, and then add the message handler functions shown in the table
below. To add a message handler function, click on an object ID, click on a message, and click the Add
Function button. If the Add Member Function dialog box appears, type the function name and click the OK
button.
Object ID Message Member Function

CActiveXDialog WM_INITDIALOG OnInitDialog (virtual function)

IDC_CALENDAR1 NewMonth (event) OnNewMonthCalendar1

IDC_SELECTDATE BN_CLICKED OnSelectDate

IDC_NEXTWEEK BN_CLICKED OnNextWeek

IDOK BN_CLICKED OnOK (virtual function)


- 103 -

Use ClassWizard to add data members to the CActiveXDialog class. Click on the Member Variables tab, and
then add the data members as shown in the illustration below.

You might think that the ClassWizard ActiveX Events tab is for mapping ActiveX control events
in a container. That's not true: it's for ActiveX control developers who are defining events for a
control.

Edit the CActiveXDialog class. Add the m_varValue and m_BackColor data members, and then edit the code
for the five handler functions OnInitDialog, OnNewMonthCalendar1, OnSelectDate, OnNextWeek, and OnOK.
Figure 8-2 shows all the code for the dialog class, with new code in boldface.
ACTIVEXDIALOG.H
//{{AFX_INCLUDES()
#include "calendar.h"
//}}AFX_INCLUDES
#if !defined(AFX_ACTIVEXDIALOG_H__1917789D_6F24_11D0_8FD9_00C04FC2A0C2__INCLUDED_)
#define AFX_ACTIVEXDIALOG_H__1917789D_6F24_11D0_8FD9_00C04FC2A0C2__INCLUDED_

#if _MSC_VER > 1000


#pragma once
#endif // _MSC_VER > 1000

// ActiveXDialog.h : header file


//

//////////////////////////////////////////////////////////////////////
// CActiveXDialog dialog
class CActiveXDialog : public CDialog

{
// Construction
public:
CActiveXDialog(CWnd* pParent = NULL); // standard constructor

// Dialog Data
//{{AFX_DATA(CActiveXDialog)
enum { IDD = IDD_ACTIVEXDIALOG };
CCalendar m_calendar;
short m_sDay;
- 104 -

short m_sMonth;
short m_sYear;
//}}AFX_DATA
COleVariant m_varValue;
unsigned long m_BackColor;

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CActiveXDialog)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV
// support
//}}AFX_VIRTUAL

// Implementation
protected:

// Generated message map functions


//{{AFX_MSG(CActiveXDialog)
virtual BOOL OnInitDialog();
afx_msg void OnNewMonthCalendar1();
afx_msg void OnSelectDate();
afx_msg void OnNextWeek();
virtual void OnOK();
DECLARE_EVENTSINK_MAP()
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional
// declarations immediately before the previous line.

#endif // !
defined(AFX_ACTIVEXDIALOG_H__1917789D_6F24_11D0_8FD9_00C04FC2A0C2__INCLUDED_)
ACTIVEXDIALOG.CPP
// ActiveXDialog.cpp : implementation file
//

#include "stdafx.h"
#include "ex08a.h"
#include "ActiveXDialog.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////////////
// CActiveXDialog dialog

CActiveXDialog::CActiveXDialog(CWnd* pParent /*=NULL*/)


: CDialog(CActiveXDialog::IDD, pParent)
- 105 -

{
//{{AFX_DATA_INIT(CActiveXDialog)
m_sDay = 0;
m_sMonth = 0;
m_sYear = 0;
//}}AFX_DATA_INIT
m_BackColor = 0x8000000F;
}

void CActiveXDialog::DoDataExchange(CDataExchange* pDX)


{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CActiveXDialog)
DDX_Control(pDX, IDC_CALENDAR1, m_calendar);
DDX_Text(pDX, IDC_DAY, m_sDay);
DDX_Text(pDX, IDC_MONTH, m_sMonth);
DDX_Text(pDX, IDC_YEAR, m_sYear);
//}}AFX_DATA_MAP
DDX_OCColor(pDX, IDC_CALENDAR1, DISPID_BACKCOLOR, m_BackColor);
}

BEGIN_MESSAGE_MAP(CActiveXDialog, CDialog)
//{{AFX_MSG_MAP(CActiveXDialog)
ON_BN_CLICKED(IDC_SELECTDATE, OnSelectDate)
ON_BN_CLICKED(IDC_NEXTWEEK, OnNextWeek)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//////////////////////////////////////////////////////////////////////
// CActiveXDialog message handlers

BEGIN_EVENTSINK_MAP(CActiveXDialog, CDialog)
//{{AFX_EVENTSINK_MAP(CActiveXDialog)
ON_EVENT(CActiveXDialog, IDC_CALENDAR1, 3 /* NewMonth */, OnNewMonthCalendar1, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

BOOL CActiveXDialog::OnInitDialog()
{
CDialog::OnInitDialog();
m_calendar.SetValue(m_varValue); // no DDX for VARIANTs
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE

}
void CActiveXDialog::OnNewMonthCalendar1()
{
AfxMessageBox("EVENT: CActiveXDialog::OnNewMonthCalendar1");
}

void CActiveXDialog::OnSelectDate()
{
CDataExchange dx(this, TRUE);
DDX_Text(&dx, IDC_DAY, m_sDay);
DDX_Text(&dx, IDC_MONTH, m_sMonth);
- 106 -

DDX_Text(&dx, IDC_YEAR, m_sYear);


m_calendar.SetDay(m_sDay);
m_calendar.SetMonth(m_sMonth);
m_calendar.SetYear(m_sYear);
}

void CActiveXDialog::OnNextWeek()
{
m_calendar.NextWeek();
}

void CActiveXDialog::OnOK()
{
CDialog::OnOK();
m_varValue = m_calendar.GetValue(); // no DDX for VARIANTs
}
Figure 8-2. Code for the CActiveXDialog class.
The OnSelectDate function is called when the user clicks the Select Date button. The function gets the day,
month, and year values from the three edit controls and transfers them to the control's properties. ClassWizard
can't add DDX code for the BackColor property, so you must add it by hand. In addition, there's no DDX code
for VARIANT types, so you must add code to the OnInitDialog and OnOK functions to set and retrieve the date
with the control's Value property.
Connect the dialog to the view. Use ClassWizard to map the WM_LBUTTONDOWN message, and then edit
the handler function as follows:
void CEx08aView::OnLButtonDown(UINT nFlags, CPoint point)
{
CActiveXDialog dlg;
dlg.m_BackColor = RGB(255, 251, 240); // light yellow
COleDateTime today = COleDateTime::GetCurrentTime();
dlg.m_varValue = COleDateTime(today.GetYear(), today.GetMonth(),
today.GetDay(), 0, 0, 0);
if (dlg.DoModal() == IDOK) {
COleDateTime date(dlg.m_varValue);
AfxMessageBox(date.Format("%B %d, %Y"));
}
}
The code sets the background color to light yellow and the date to today's date, displays the modal dialog, and
reports the date returned by the Calendar control. You'll need to include ActiveXDialog.h in ex08aView.cpp.
Edit the virtual OnDraw function in the file ex08aView.cpp. To prompt the user to press the left mouse button,
replace the code in the view class OnDraw function with this single line:
pDC->TextOut(0, 0, "Press the left mouse button here.");
Build and test the EX08A application. Open the dialog, enter a date in the three edit controls, and then click the
Select Date button. Click the Next Week button. Try moving the selected date directly to a new month, and
observe the message box that is triggered by the NewMonth event. Watch for the final date in another message
box when you click OK. Press the F1 key for help on the Calendar control.
For Win32 Programmers
If you use a text editor to look inside the ex08a.rc file, you might be quite mystified. Here's the entry
for the Calendar control in the ActiveX Dialog template:
CONTROL "",IDC_CALENDAR1,
"{8E27C92B-1264-101C-8A2F-040224009C02}",
WS_TABSTOP,7,7,217,113
There's a 32-digit number sequence where the window class name should be. What's going on?
Actually, the resource template isn't the one that Windows sees. The CDialog::DoModal function
"preprocesses" the resource template before passing it on to the dialog box procedure within
Windows. It strips out all the ActiveX controls and creates the dialog window without them. Then it
loads the controls (based on their 32-digit identification numbers, called CLSIDs) and activates them
- 107 -

in place, causing them to create their own windows in the correct places. The initial values for the
properties you set in the dialog editor are stored in binary form inside the project's custom DLGINIT
resource.
When the modal dialog runs, the MFC code coordinates the messages sent to the dialog window
both by the ordinary controls and by the ActiveX controls. This allows the user to tab between all the
controls in the dialog, even though the ActiveX controls are not part of the actual dialog template.
When you call the member functions for the control object, you might think you're calling functions
for a child window. The control window is quite far removed, but MFC steps in to make it seem as if
you're communicating with a real child window. In ActiveX terminology, the container owns a site,
which is not a window. You call functions for the site, and ActiveX and MFC make the connection to
the underlying window in the ActiveX control.
The container window is an object of a class derived from CWnd. The control site is also an object of
a class derived from CWnd—the ActiveX control wrapper class. That means that the CWnd class
has built-in support for both containers and sites.
What you're seeing here is MFC ActiveX control support grafted onto regular Windows. Maybe some
future Windows version will have more direct support for ActiveX Controls. As a matter of fact,
ActiveX versions of the Windows common controls already exist.
56. ActiveX Controls in HTML Files
You've seen the ActiveX Calendar control in an MFC modal dialog. You can use the same control in a Web page.
The following HTML code will work (assuming the person reading the page has the Calendar control installed and
registered on his or her machine):
<OBJECT
CLASSID="clsid:8E27C92B-1264-101C-8A2F-040224009C02"
WIDTH=300 HEIGHT=200 BORDER=1 HSPACE=5 ID=calendar>
<PARAM NAME="Day" VALUE=7>
<PARAM NAME="Month" VALUE=11>
<PARAM NAME="Year" VALUE=1998>
</OBJECT>
The CLASSID attribute (the same number that was in the EX08A dialog resource) identifies the Calendar control in
the Registry. A browser can download an ActiveX control.
57. Creating ActiveX Controls at Runtime
You've seen how to use the dialog editor to insert ActiveX controls at design time. If you need to create an ActiveX
control at runtime without a resource template entry, here are the programming steps:
Insert the component into your project. ClassWizard will create the files for a wrapper class.
Add an embedded ActiveX control wrapper class data member to your dialog class or other C++ window class.
An embedded C++ object is then constructed and destroyed along with the window object.
Choose Resource Symbols from Visual C++'s View menu. Add an ID constant for the new control.
If the parent window is a dialog, use ClassWizard to map the dialog's WM_INITDIALOG message, thus
overriding CDialog-::OnInitDialog. For other windows, use ClassWizard to map the WM_CREATE message.
The new function should call the embedded control class's Create member function. This call indirectly displays
the new control in the dialog. The control will be properly destroyed when the parent window is destroyed.
In the parent window class, manually add the necessary event message handlers and prototypes for your new
control. Don't forget to add the event sink map macros.

ClassWizard doesn't help you with event sink maps when you add a dynamic ActiveX control to a
project. Consider inserting the target control in a dialog in another temporary project. After you're
finished mapping events, simply copy the event sink map code to the parent window class in your
main project.
58. The EX08B Example—The Web Browser ActiveX Control
Microsoft Internet Explorer 4.x has become a leading Web browser. I was surprised to find out that most of its
functionality is contained in one big ActiveX control, Shdocvw.dll. When you run Internet Explorer, you launch a small
shell program that loads this Web Browser control in its main window.

You can find complete documentation for the Web Browser control's properties, methods, and
events in the Internet SDK, downloadable from http://www.microsoft.com. This documentation is in
HTML form, of course.
- 108 -

Because of this modular architecture, you can write your own custom browser program with very little effort. EX08B
creates a two-window browser that displays a search engine page side-by-side with the target page, as shown here.

This view window contains two Web Browser controls that are sized to occupy the entire client area. When the user
clicks an item in the search (right-hand) control, the program intercepts the command and routes it to the target (left-
hand) control.
Here are the steps for building the example:
Make sure the Web Browser control is registered. You undoubtedly have Microsoft Internet Explorer 4.x
installed, since Visual C++ 6.0 requires it, so the Web Browser control should be registered. You can download
Internet Explorer from http://www.microsoft.com if necessary.
Run AppWizard to produce \vcpp32\ex08b\ex08b. Accept all the default settings but two: except select Single
Document and deselect Printing And Print Preview. Make sure the ActiveX Controls option is checked as in
EX08A.
Install the Web Browser control in the EX08B project. Choose Add To Project from Visual C++'s Project menu,
and choose Components And Controls from the submenu. Select Registered ActiveX Controls, and then
choose Microsoft Web Browser. Visual C++ will generate the wrapper class CWebBrowser and add the files to
your project.
Add two CWebBrowser data members to the CEx08bView class. Click on the ClassView tab in the Workspace
window, and then right-click the CEx08bView class. Choose Add Member Variable, and fill in the dialog as
shown here.

Repeat for m_target. ClassWizard adds an #include statement for the webbrowser.h file.
Add the child window ID constants for the two controls. Select Resource Symbols from Visual C++'s View
menu, and then add the symbols ID_BROWSER_SEARCH and ID_BROWSER_TARGET.
Add a static character array data member for the AltaVista URL. Add the following static data member to the
class declaration in ex08bView.h:
private:
static const char s_engineAltavista[];
Then add the following definition in ex08bView.cpp, outside any function:
const char CEx08bView::s_engineAltavista[] =
- 109 -

"http://altavista.digital.com/";
Use ClassWizard to map the view's WM_CREATE and WM_SIZE messages. Edit the handler code in
ex08bView.cpp as follows:
int CEx08bView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;

DWORD dwStyle = WS_VISIBLE | WS_CHILD;


if (m_search.Create(NULL, dwStyle, CRect(0, 0, 100, 100),
this, ID_BROWSER_SEARCH) == 0) {
AfxMessageBox("Unable to create search control!\n");
return -1;
}
m_search.Navigate(s_engineAltavista, NULL, NULL, NULL, NULL);

if (m_target.Create(NULL, dwStyle, CRect(0, 0, 100, 100),


this, ID_BROWSER_TARGET) == 0) {
AfxMessageBox("Unable to create target control!\n");
return -1;
}
m_target.GoHome(); // as defined in Internet Explorer 4 options

return 0;
}

void CEx08bView::OnSize(UINT nType, int cx, int cy)


{
CView::OnSize(nType, cx, cy);

CRect rectClient;
GetClientRect(rectClient);
CRect rectBrowse(rectClient);
rectBrowse.right = rectClient.right / 2;
CRect rectSearch(rectClient);
rectSearch.left = rectClient.right / 2;

m_target.SetWidth(rectBrowse.right - rectBrowse.left);
m_target.SetHeight(rectBrowse.bottom - rectBrowse.top);
m_target.UpdateWindow();

m_search.SetLeft(rectSearch.left);
m_search.SetWidth(rectSearch.right - rectSearch.left);
m_search.SetHeight(rectSearch.bottom - rectSearch.top);
m_search.UpdateWindow();
}
The OnCreate function creates two browser windows inside the view window. The right-hand browser displays
the top-level AltaVista page, and the left-hand browser displays the "home" page as defined through the
Internet icon in the Control Panel. The OnSize function, called whenever the view window changes size,
ensures that the browser windows completely cover the view window. The CWebBrowser member functions
SetWidth and SetHeight set the browser's Width and Height properties.
Add the event sink macros in the CEx08bView files. ClassWizard can't map events from a dynamic ActiveX
control, so you must do it manually. Add the following lines inside the class declaration in the file ex08bView.h:
protected:
afx_msg void OnBeforeNavigateExplorer1(LPCTSTR URL, long Flags, LPCTSTR TargetFrameName, VARIANT
FAR* PostData, LPCTSTR Headers, BOOL FAR* Cancel);
- 110 -

afx_msg void OnTitleChangeExplorer2(LPCTSTR Text);


DECLARE_EVENTSINK_MAP()
Then add the following code in ex08bView.cpp:
BEGIN_EVENTSINK_MAP(CEx08bView, CView)
ON_EVENT(CEx08bView, ID_BROWSER_SEARCH, 100, OnBeforeNavigateExplorer1, VTS_BSTR VTS_I4
VTS_BSTR VTS_PVARIANT VTS_BSTR VTS_PBOOL)
ON_EVENT(CEx08bView, ID_BROWSER_TARGET, 113, OnTitleChangeExplorer2, VTS_BSTR)
END_EVENTSINK_MAP()
Add two event handler functions. Add the following member functions in ex08bView.cpp:
void CEx08bView::OnBeforeNavigateExplorer1(LPCTSTR URL,
long Flags, LPCTSTR TargetFrameName,
VARIANT FAR* PostData, LPCTSTR Headers, BOOL FAR* Cancel)
{
TRACE("CEx08bView::OnBeforeNavigateExplorer1 -- URL = %s\n", URL);

if (!strnicmp(URL, s_engineAltavista, strlen(s_engineAltavista))) {


return;
}
m_target.Navigate(URL, NULL, NULL, PostData, NULL);
*Cancel = TRUE;
}

void CEx08bView::OnTitleChangeExplorer2(LPCTSTR Text)


{
// Careful! Event could fire before we're ready.
CWnd* pWnd = AfxGetApp()->m_pMainWnd;
if (pWnd != NULL) {
if (::IsWindow(pWnd->m_hWnd)) {
pWnd->SetWindowText(Text);
}
}
}
The OnBeforeNavigateExplorer1 handler is called when the user clicks on a link in the search page. The
function compares the clicked URL (in the URL string parameter) with the search engine URL. If they match,
the navigation proceeds in the search window; otherwise, the navigation is cancelled and the Navigate method
is called for the target window. The OnTitleChangeExplorer2 handler updates the EX08B window title to match
the title on the target page.
Build and test the EX08B application. Search for something on the AltaVista page, and then watch the
information appear in the target page.
59. The EX08C Example—A Complete Dual-Window Web Browser
I deliberately kept the EX08B example simple to clearly illustrate the use of the Web Browser control. However, I
couldn't resist upgrading the program so that I could use it as my primary Internet browser. The result is EX08C,
which uses MFC features described in later chapters of this book—in particular the features below.
A splitter window with moveable vertical bar browser windows
Use of the Registry to "remember" the search and target pages
Printing of both search and target pages
Support for multiple search engines
Toolbar buttons for navigation, printing, and search engine selection
Status bar display of activity and the selected URL
If EX08B runs, \vcpp32\Debug\ex08c should run also. I'm sure you'll have your own ideas for further customization.
Once you've studied the rest of the book, you'll be able to take control of this project from the CD-ROM.
60. Picture Properties
Some ActiveX controls support picture properties, which can accommodate bitmaps, metafiles, and icons. If an
ActiveX control has at least one picture property, ClassWizard generates a CPicture class in your project during the
control's installation. You don't need to use this CPicture class, but you must use the MFC class CPictureHolder. To
access the CPictureHolder class declaration and code, you need the following line in StdAfx.h:
- 111 -

#include <afxctl.h>
Suppose you have an ActiveX control with a picture property named Picture. Here's how you set the Picture property
to a bitmap in your program's resources:
CPictureHolder pict;
pict.CreateFromBitmap(IDB_MYBITMAP); // from project's resources
m_control.SetPicture(pict.GetPictureDispatch());

If you include the AfxCtl.h file, you can't statically link your program with the MFC library. If you need
a stand-alone program that supports picture properties, you'll have to borrow code from the
CPictureHolder class, located in the \Program Files\Microsoft Visual Studio\VC98\mfc\src\ctlpict.cpp
file.
61. Bindable Properties—Change Notifications
If an ActiveX control has a property designated as bindable, the control will send an OnChanged notification to its
container when the value of the property changes inside the control. In addition, the control can send an
OnRequestEdit notification for a property whose value is about to change but has not yet changed. If the container
returns FALSE from its OnRequestEdit handler, the control should not change the property value.
MFC fully supports property change notifications in ActiveX control containers, but as of Visual C++ version 6.0, no
ClassWizard support was available. That means you must manually add entries to your container class's event sink
map.
Suppose you have an ActiveX control with a bindable property named Note with a dispatch ID of 4. You add an
ON_PROPNOTIFY macro to the EVENTSINK macros in this way:
BEGIN_EVENTSINK_MAP(CAboutDlg, CDialog)
//{{AFX_EVENTSINK_MAP(CAboutDlg)
// ClassWizard places other event notification macros here
//}}AFX_EVENTSINK_MAP
ON_PROPNOTIFY(CAboutDlg, IDC_MYCTRL1, 4, OnNoteRequestEdit, OnNoteChanged)
END_EVENTSINK_MAP()
You must then code the OnNoteRequestEdit and OnNoteChanged functions with return types and parameter types
exactly as shown here:
BOOL CMyDlg::OnNoteRequestEdit(BOOL* pb)
{
TRACE("CMyDlg::OnNoteRequestEdit\n");
*pb = TRUE; // TRUE means change request granted
return TRUE;
}

BOOL CMyDlg::OnNoteChanged()
{
TRACE("CMyDlg::OnNoteChanged\n");
return TRUE;
}
You'll also need corresponding prototypes in the class header, as shown here:
afx_msg BOOL OnNoteRequestEdit(BOOL* pb);
afx_msg BOOL OnNoteChanged();
62. Other ActiveX Controls
You'll probably notice that your disk fills up with ActiveX controls, especially if you accept controls from Web sites.
Most of these controls are difficult to use unless you have the documentation on hand, but you can have fun
experimenting. Try the Marquee.ocx control that is distributed with Visual C++ 6.0. It works fine in both MFC
programs and HTML files. The trick is to set the szURL property to the name of another HTML file that contains the
text to display in the scrolling marquee window.
Many ActiveX controls were designed for use by Visual Basic programmers. The SysInfo.ocx control that comes with
Visual C++, for example, lets you retrieve system parameters as property values. This isn't of much use to a C++
programmer, however, because you can make the equivalent Win32 calls anytime. Unlike the many objects provided
by MFC, ActiveX controls are binary objects that are not extensible. For example, you cannot add a property or event
to an ActiveX control. Nor can you use many C++ object-oriented techniques like polymorphism with ActiveX
controls. Another downside of ActiveX controls is they are not compatible with many advanced MFC concepts such
as the document/view architecture, which we will cover later.
- 112 -

Chapter Nine
63. Internet Explorer 4 Common Controls
When Microsoft developers released Internet Explorer 4 (IE4), they included a new and improved version of the
COMCTL32.DLL, which houses Microsoft Windows Common Controls. Since this update to the common controls
was not part of an operating system release, Microsoft calls the update Internet Explorer 4 Common Controls. IE4
Common Controls updates all of the existing controls and adds a variety of advanced new controls. Microsoft Visual
C++ 6.0 and Microsoft Foundation Class (MFC) 6.0 have added a great deal of support for these new controls. In this
chapter, we'll look at the new controls and show examples of how to use each one. If you haven't worked with
Windows controls or Windows Common Controls, be sure you read Chapter 6 before proceeding with IE4 Common
Controls.

While Microsoft Windows 95 and Microsoft Windows NT 4.0 do not include the new
COMCTL32.DLL, future versions of Windows will. To be safe, you will need to redistribute the
COMCTL32.DLL for these existing operating systems as part of your installation. Currently you must
ship a "developer's edition" of Internet Explorer to be able to redistribute these controls. However,
this might change once a version of Windows ships with the updated controls. Be sure you check
www.microsoft.com/msdn for the latest news on this subject.
64. An Introduction to the New Internet Explorer 4 Common Controls
Example EX09A uses each of the new IE4 common controls. Figure 9-1 shows the dialog from that example. Refer
to it when you read the control descriptions that follow.

Figure 9-1. The new Internet Explorer 4 Common Controls dialog.


64.1 The Date and Time Picker
A common field on a dialog is a place for the user to enter a date and time. Before IE4 controls provided the date and
time picker, developers had to either use a third-party control or subclass an MFC edit control to do significant data
validation to ensure that the entered date was valid. Fortunately, the new date and time picker control is provided as
an advanced control that prompts the user for a date or time while offering the developer a wide variety of styles and
options. For example, dates can be displayed in short formats (8/14/68) or long formats (August 14, 1968). A time
mode lets the user enter a time using a familiar hours/minutes/seconds AM/PM format.
The control also lets you decide if you want the user to select the date via in-place editing, a pull-down calendar, or a
spin button. Several selection options are available including single and multiple select (for a range of dates) and the
ability to turn on and off the "circling" in red ink of the current date. The control even has a mode that lets the user
select "no date" via a check box. In Figure 9-1, the first four controls on the left illustrate the variety of configurations
available with the date and time picker control.
The new MFC 6.0 class CDateTimeCtrl provides the MFC interface to the IE4 date and time picker common control.
This class provides a variety of notifications that enhance the programmability of the control. CDateTimeCtrl provides
member functions for dealing with either CTime or COleDateTime time structures.
You set the date and time in a CDateTimeCtrl using the SetTime member function. You can retrieve the date and
time via the GetTime function. You can create custom formats using the SetFormat member function and change a
variety of other configurations using the CDateTimeCtrl interface.
CTime vs. COleDateTime
- 113 -

Most "longtime" MFC developers are accustomed to using the CTime class. However, since CTime's
valid dates are limited to dates between January 1, 1970, and January 18, 2038, many developers
are looking for an alternative. One popular alternative is COleDateTime, which is provided for OLE
automation support and handles dates from 1 January 100 through 31 December 9999. Both
classes have various pros and cons. For example, CTime handles all the issues of daylight savings
time, while COleDateTime does not.
With the Year 2000 crisis looming ahead, many developers choose COleDateTime because of its
much larger range. Any application that uses CTime will need to be reworked in approximately 40
years, since the maximum value is the year 2038. To see this limitation in action, select a date
outside the CTime range in EX09A. The class you decide to use will depend on your particular
needs and the potential longevity of your application.
64.2 The Month Calendar
The large display at the bottom left of Figure 9-1 is a Month Calendar. Like the date and time picker control, the
month calendar control lets the user choose a date. However, the month calendar control can also be used to
implement a small Personal Information Manager (PIM) in your applications. You can show as many months as room
provides—from one month to a year's worth of months, if you want. EX09A uses the month calendar control to show
only two months.
The month calendar control supports single or multiple selection and allows you to display a variety of different
options such as numbered months and a circled "today's date." Notifications for the control let the developer specify
which dates are in boldface. It is entirely up to the developer to decide what boldface dates might represent. For
example, you could use the bold feature to indicate holidays, appointments, or unusable dates. The MFC 6.0 class
CMonthCalCtrl implements this control.
To initialize the CMonthCalCtrl class, you can call the SetToday() member function. CMonthCalCtrl provides
members that deal with both CTime and COleDateTime, including SetToday().
64.3 The Internet Protocol Address Control
If you write an application that uses any form of Internet or TCP/IP functionality, you might need to prompt the user
for an Internet Protocol (IP) Address. The IE4 common controls include an IP address edit control as shown in the
top right of Figure 9-1. In addition to letting the user enter a 4-byte IP address, this control performs an automatic
validation of the entered IP address. CIPAddressCtrl provides MFC support for the IP address control.
An IP address consists of four "fields" as shown in Figure 9-2. The fields are numbered from left to right.

Figure 9-2. The fields of an IP address control.


To initialize an IP address control, you call the SetAddress member function in your OnInitDialog function.
SetAddress takes a DWORD, with each BYTE in the DWORD representing one of the fields. In your message
handlers, you can call the GetAddress member function to retrieve a DWORD or a series of BYTES to retrieve the
various values of the four IP address fields.
64.4 The Extended Combo Box
The "old-fashioned" combo box was developed in the early days of Windows. Its age and inflexible design have been
the source of a great deal of developer confusion. With the IE4 controls, Microsoft has decided to release a much
more flexible version of the combo box called the extended combo box.
The extended combo box gives the developer much easier access to and control over the edit-control portion of the
combo box. In addition, the extended combo box lets you attach an image list to the items in the combo box. You can
display graphics in the extended combo box easily, especially when compared with the old days of using owner-
drawn combo boxes. Each item in the extended combo box can be associated with three images: a selected image,
an unselected image, and an overlay image. These three images can be used to provide a variety of graphical
displays in the combo box, as we'll see in the EX09A sample. The bottom two combo boxes in Figure 9-1 are both
extended combo boxes. The MFC CComboBoxEx class provides comprehensive extended combo box support.
Like the list control introduced in Chapter 6, CComboBoxEx can be attached to a CImageList that will automatically
display graphics next to the text in the extended combo box. If you are already familiar with CComboBox,
- 114 -

CComboBoxEx might cause some confusion: instead of containing strings, the extended combo box contains items
of type COMBOBOXEXITEM, a structure that consists of the following fields:
UINT mask—A set of bit flags that specify which operations are to be performed using the structure. For example,
set the CBEIF_IMAGE flag if the image field is to be set or retrieved in an operation.
int iItem—The extended combo box item number. Like the older style of combo box, the extended combo box
uses zero-based indexing.
LPSTR pszText—The text of the item.
int cchTextMax—The length of the buffer available in pszText.
int iImage—Zero-based index into an associated image list.
int iSelectedImage—Index of the image in the image list to be used to represent the "selected" state.
int iOverlay—Index of the image in the image list to be used to overlay the current image.
int iIndent—Number of 10-pixel indentation spaces.
LPARAM lParam—32-bit parameter for the item.
You will see first-hand how to use this structure in the EX09A example.
65. The EX09A Example
To illustrate how to take advantage of the new Internet Explorer 4 Common Controls, we'll build a dialog that
demonstrates how to create and program each control type. The steps required to create the dialog are shown
below.
Run AppWizard to generate the EX09A project. Choose New from the Visual C++ File menu, and then select
Microsoft AppWizard (exe) from the Projects page. Accept all the defaults but one: choose Single Document
Interface (SDI). The options and the default class names are shown here.

Create a new dialog resource with ID IDD_DIALOG1. Place the controls as shown in Figure 9-1.
You can drag the controls from the control palette, shown in Chapter 6. Remember that IE4 Common Controls
are at the bottom of the palette. The following table lists the control types and their IDs.
Tab Sequence Control Type Child Window ID

1 Group Box IDC_STATIC

2 Static IDC_STATIC

3 Date Time Picker IDC_DATETIMEPICKER1


- 115 -

4 Static IDC_STATIC1

5 Static IDC_STATIC

6 Date Time Picker IDC_DATETIMEPICKER2

7 Static IDC_STATIC2

8 Static IDC_STATIC

9 Date Time Picker IDC_DATETIMEPICKER3

10 Static IDC_STATIC3

11 Static IDC_STATIC

12 Date Time Picker IDC_DATETIMEPICKER4

13 Static IDC_STATIC4

14 Static IDC_STATIC

15 Month Calendar IDC_MONTHCALENDAR

16 Static IDC_STATIC5
17 Group Box IDC_STATIC

18 Static IDC_STATIC

19 IP Address IDC_IPADDRESS1

20 Static IDC_STATIC6

21 Group Box IDC_STATIC

22 Static IDC_STATIC

23 Extended Combo Box IDC_COMBOBOXEX1

24 Static IDC_STATIC7

25 Static IDC_STATIC

26 Extended Combo Box IDC_COMBOBOXEX2

27 Static IDC_STATIC8

28 Pushbutton IDOK

29 Pushbutton IDCANCEL
The following figure shows each control and its appropriate tab order.
- 116 -

Until we set some properties, your dialog will not look exactly like the one in Figure 9-1.
Use ClassWizard to create a new class, CDialog1, derived from CDialog. ClassWizard will automatically prompt
you to create this class because it knows that the IDD_DIALOG1 resource exists without an associated C++
class. Go ahead and create a message handler for the WM_INITDIALOG message.
Set the properties for the dialog's controls. To demonstrate the full range of controls, we will need to set a
variety of properties on each of the IE4 common controls in the example. Here is a brief overview of each
property you will need to set:
The Short Date and Time Picker. To set up the first date and time picker control to use the short format, select
the properties for IDC_DATETIMEPICKER1, as shown in the following figure.

The Long Date and Time Picker. Now configure the second date and time picker control
(IDC_DATETIMEPICKER2) to use the long format as shown below.

The Short and NULL Date and Time Picker. This is the third date and time picker control,
IDC_DATETIMEPICKER3. Configure this third date and time picker to use the short format and the styles
shown here.
- 117 -

The Time Picker. The fourth date and time picker control, IDC_DATETIMEPICKER4, is configured to let the
user choose time. To configure this control, select Time from the Format combo box on the Styles tab as
shown.

The Month View. To configure the month view, you will need to set a variety of styles. First, from the Styles
tab, choose Day States, as shown here.

If we leave the default styles, the month view does not look like a control on the dialog. There are no borders
drawn at all. To make the control fit in with the other controls on the dialog, select Client Edge and Static
Edge from the Extended Styles tab, as shown below.

The IP Address.This control (IDC_IPADDRESS1) does not require any special properties.
The First Extended Combo Box.Make sure that you enter some items, as shown here, and also make sure
the list is tall enough to display several items.
- 118 -

The Second Extended Combo Box.Enter three items: Tweety, Mack, and Jaws. Later in the example, we will
use these items to show one of the ways to draw graphics in an extended combo box.

Add the CDialog1 variables. Start ClassWizard and click on the Member Variables tab to view the Member
Variables page. Enter the following member variables for each control listed.
Control ID Data Member Type

IDC_DATETIMEPICKER1 m_MonthCal1 CDateTimeCtrl

IDC_DATETIMEPICKER2 m_MonthCal2 CDateTimeCtrl

IDC_DATETIMEPICKER3 m_MonthCal3 CDateTimeCtrl

IDC_DATETIMEPICKER4 m_MonthCal4 CDateTimeCtrl

vIDC_IPADDRESS1 m_ptrIPCtrl CIPAddressCtrl

IDC_MONTHCALENDAR1 m_MonthCal5 CMonthCalCtrl

IDC_STATIC1 m_strDate1 CString

IDC_STATIC2 m_strDate2 CString

IDC_STATIC3 m_strDate3 CString

IDC_STATIC4 m_strDate4 CString

IDC_STATIC5 m_strDate5 CString

IDC_STATIC6 m_strIPValue CString

IDC_STATIC7 m_strComboEx1 CString

IDC_STATIC8 m_strComboEx2 CString


Program the short date time picker. In this example, we don't mind if the first date time picker starts with the
current date, so we don't have any OnInitDialog handling for this control. However, if we wanted to change the
date, we would make a call to SetTime for the control in OnInitDialog. At runtime, when the user selects a new
date in the first date and time picker, the companion static control should be automatically updated. To achieve
this, we need to use ClassWizard to add a handler for the DTN_DATETIMECHANGE message. Start
ClassWizard (CTRL-W) and choose IDC_DATETIMEPICKER1 from the Object IDs list and
DTN_DATETIMECHANGE from the Messages list. Accept the default message name and click OK. Repeat
this step for each of the other three IDC_DATETIMEPICKER IDs. Your ClassWizard should look like the
illustration here.
- 119 -

Next add the following code to the handler for Datetimepicker1 created by ClassWizard:
void CDialog1::OnDatetimechangeDatetimepicker1(NMHDR* pNMHDR,
LRESULT* pResult)
{
CTime ct;
m_MonthCal1.GetTime(ct);
m_strDate1.Format(_T("%02d/%02d/%2d"),
ct.GetMonth(),ct.GetDay(),ct.GetYear());
UpdateData(FALSE);
*pResult = 0;
}
This code uses the m_MonthCal1 data member that maps to the first date time picker to retrieve the time into
the CTime object variable ct. It then calls the CString::Format member function to set the companion static
string. Finally the call to UpdateData(FALSE) triggers MFC's DDX and causes the static to be automatically
updated to m_strDate1.
Program the long date time picker. Now we need to provide a similar handler for the second date time picker.
void CDialog1::OnDatetimechangeDatetimepicker2(NMHDR* pNMHDR,
LRESULT* pResult)
{
CTime ct;
m_MonthCal2.GetTime(ct);
m_strDate2.Format(_T("%02d/%02d/%2d"),
ct.GetMonth(),ct.GetDay(),ct.GetYear());
UpdateData(FALSE);

*pResult = 0;
}
Program the third date time picker. The third date time picker needs a similar handler, but since we set the
Show None style in the dialog properties, it is possible for the user to specify a NULL date by checking the
inline check box. Instead of blindly calling GetTime, we have to check the return value. If the return value of the
GetTime call is nonzero, the user has selected a NULL date. If the return value is zero, a valid date has been
selected. As in the previous two handlers, when a CTime object is returned, it is converted into a string and
automatically displayed in the companion static control.
void CDialog1::OnDatetimechangeDatetimepicker3(NMHDR* pNMHDR,
LRESULT* pResult)
{
//NOTE: this one can be null!
CTime ct;
int nRetVal = m_MonthCal3.GetTime(ct);
- 120 -

if (nRetVal) //If not zero, it's null; and if it is,


// do the right thing.
{
m_strDate3 = "NO DATE SPECIFIED!!";
}
else
{
m_strDate3.Format(_T("%02d/%02d/%2d"),ct.GetMonth(),
ct.GetDay(),ct.GetYear());
}
UpdateData(FALSE);
*pResult = 0;
}
Program the time picker. The time picker needs a similar handler, but this time the format displays
hours/minutes/seconds instead of months/days/years:
void CDialog1::OnDatetimechangeDatetimepicker4(NMHDR* pNMHDR,
LRESULT* pResult)
{
CTime ct;
m_MonthCal4.GetTime(ct);
m_strDate4.Format(_T("%02d:%02d:%2d"),
ct.GetHour(),ct.GetMinute(),ct.GetSecond());
UpdateData(FALSE);
*pResult = 0;
}
Program the Month Selector. You might think that the month selector handler is similar to the date time picker's
handler, but they are actually somewhat different. First of all, the message you need to handle for detecting
when the user has selected a new date is the MCN_SELCHANGE message. Select this message in the
ClassWizard, as shown here.

In addition to the different message handler, this control uses GetCurSel as the date time picker instead of
GetTime. The code below shows the MCN_SELCHANGE handler for the month calendar control.
void CDialog1::OnSelchangeMonthcalendar1(NMHDR* pNMHDR,
LRESULT* pResult)
{
CTime ct;
m_MonthCal5.GetCurSel(ct);
m_strDate5.Format(_T("%02d/%02d/%2d"),
ct.GetMonth(),ct.GetDay(),ct.GetYear());
- 121 -

UpdateData(FALSE);
*pResult = 0;
}
Program the IP control. First we need to make sure the control is initialized. In this example, we initialize the
control to 0 by giving it a 0 DWORD value. If you do not initialize the control, each segment will be blank. To
initialize the control, add this call to the CDialog1::OnInitDialog function:

m_ptrIPCtrl.SetAddress(0L);
Now we need to add a handler to update the companion static control whenever the IP address control
changes. First we need to add a handler for the IPN_FIELDCHANGED notification message using
ClassWizard, as shown here.

Next we need to implement the handler as follows:


void CDialog1::OnFieldchangedIpaddress1(NMHDR* pNMHDR,
LRESULT* pResult)
{
DWORD dwIPAddress;
m_ptrIPCtrl.GetAddress(dwIPAddress);

m_strIPValue.Format("%d.%d.%d.%d %x.%x.%x.%x",
HIBYTE(HIWORD(dwIPAddress)),
LOBYTE(HIWORD(dwIPAddress)),
HIBYTE(LOWORD(dwIPAddress)),
LOBYTE(LOWORD(dwIPAddress)),
HIBYTE(HIWORD(dwIPAddress)),
LOBYTE(HIWORD(dwIPAddress)),
HIBYTE(LOWORD(dwIPAddress)),
LOBYTE(LOWORD(dwIPAddress)));
UpdateData(FALSE);
*pResult = 0;
}
The first call to CIPAddressCtrl::GetAddress retrieves the current IP address into the local dwIPAddress
DWORD variable. Next we make a fairly complex call to CString::Format to deconstruct the DWORD into the
various fields. This call uses the LOWORD macro to first get to the bottom word of the DWORD and the
HIBYTE/LOBYTE macros to further deconstruct the fields in order from field 0 to field 3.
Add a handler for the first extended combo box. No special initialization is required for the extended combo
box, but we do need to handle the CBN_SELCHANGE message. The following code shows the extended
combo box handler. Can you spot the ways that this differs from a "normal" combo box control?
void CDialog1::OnSelchangeComboboxex1()
{
- 122 -

COMBOBOXEXITEM cbi;
CString str ("dummy_string");
CComboBoxEx * pCombo = (CComboBoxEx *)GetDlgItem(IDC_COMBOBOXEX1);

int nSel = pCombo->GetCurSel();


cbi.iItem = nSel;
cbi.pszText = (LPTSTR)(LPCTSTR)str;
cbi.mask = CBEIF_TEXT;
cbi.cchTextMax = str.GetLength();
pCombo->GetItem(&cbi);
SetDlgItemText(IDC_STATIC7,str);
return;
}
The first thing you probably noticed is the use of the COMBOBOXEXITEM structure for the extended combo
box instead of the plain integers used for items in an older combo box. Once the handler retrieves the item, it
extracts the string and calls SetDlgItemText to update the companion static control.
Add Images to the Items in the second extended combo box. The first extended combo box does not need any
special programming. It is used to demonstrate how to implement a simple extended combo box very similar to
the older, nonextended combo box. The second combo box requires a good bit of programming. First we
created six bitmaps and eight icons that we need to add to the resources for the project, as shown in the
following illustration.

Of course, you are free to grab these images from the companion CD instead of recreating them all by hand, or
you can choose to use any bitmaps and icons.
There are two ways to add our graphics to an extended combo box. The first method is to attach images to
existing combo box items. (Remember that we used the dialog editor to add the Tweety, Mack, and Jaws items
to the combo box.) The second method is to add new items and specify their corresponding images at the time
of addition.
- 123 -

Before we start adding graphics to the extended combo box, let's create a public CImageList data member in
the CDialog1 class named m_imageList. Be sure you add the data member to the header file (Dialog1.h) for the
class.
Now we can add some of the bitmap images to the image list and then "attach" the images to the three items
already in the extended combo box. Add the following code to your CDialog1's OnInitDialog method to achieve
this:
//Initialize the IDC_COMBOBOXEX2
CComboBoxEx* pCombo =
(CComboBoxEx*) GetDlgItem(IDC_COMBOBOXEX2);
//First let's add images to the items there.
//We have six images in bitmaps to match to our strings:

//CImageList * pImageList = new CImageList();


m_imageList.Create(32,16,ILC_MASK,12,4);

CBitmap bitmap;

bitmap.LoadBitmap(IDB_BMBIRD);
m_imageList.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();

bitmap.LoadBitmap(IDB_BMBIRDSELECTED);
m_imageList.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();

bitmap.LoadBitmap(IDB_BMDOG);
m_imageList.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();

bitmap.LoadBitmap(IDB_BMDOGSELECTED);
m_imageList.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();

bitmap.LoadBitmap(IDB_BMFISH);
m_imageList.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();

bitmap.LoadBitmap(IDB_BMFISHSELECTED);
m_imageList.Add(&bitmap, (COLORREF)0xFFFFFF);
bitmap.DeleteObject();

//Set the imagelist


pCombo->SetImageList(&m_imageList);
//Now attach the images to the items in the list.
COMBOBOXEXITEM cbi;
cbi.mask = CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_INDENT;
CString strTemp;
int nBitmapCount = 0;
for (int nCount = 0;nCount < 3;nCount++)
{
cbi.iItem = nCount;
cbi.pszText = (LPTSTR)(LPCTSTR)strTemp;
cbi.cchTextMax = 256;
pCombo->GetItem(&cbi);
cbi.iImage = nBitmapCount++;
cbi.iSelectedImage = nBitmapCount++;
- 124 -

cbi.iIndent = (nCount & 0x03);


pCombo->SetItem(&cbi);

}
First the extended combo box initialization code creates a pointer to the control using GetDlgItem. Next it calls
Create to create memory for the images to be added and to initialize the image list. The next series of calls
loads each bitmap, adds them to the image list, and then deletes the resource allocated in the load.
CComboBoxEx::SetImageList is called to associate the m_imageList with the extended combo box. Next a
COMBOBOXEXITEM structure is initialized with a mask, and then the for loop iterates from 0 through 2, setting
the selected and unselected images with each pass through the loop. The variable nBitmapCount increments
through the image list to ensure that the correct image ID is put into the COMBOBOXEXITEM structure. The for
loop makes a call to CComboBoxEx::GetItem to retrieve the COMBOBOXEXITEM structure for each item in the
extended combo box. Then the loop sets up the images for the list item and finally calls
CComboBoxEx::SetItem to put the modified COMBOBOXEXITEM structure back into the extended combo box
and complete the association of images with the existing items in the list.
Add Items to the Extended Combobox. The other technique available for putting images into an extended
combo box is to add them dynamically, as shown in the code added to OnInitDialog below:
HICON hIcon[8];
int n;
//Now let's insert some color icons
hIcon[0] = AfxGetApp()->LoadIcon(IDI_WHITE);
hIcon[1] = AfxGetApp()->LoadIcon(IDI_BLACK);
hIcon[2] = AfxGetApp()->LoadIcon(IDI_RED);
hIcon[3] = AfxGetApp()->LoadIcon(IDI_BLUE);
hIcon[4] = AfxGetApp()->LoadIcon(IDI_YELLOW);
hIcon[5] = AfxGetApp()->LoadIcon(IDI_CYAN);
hIcon[6] = AfxGetApp()->LoadIcon(IDI_PURPLE);
hIcon[7] = AfxGetApp()->LoadIcon(IDI_GREEN);
for (n = 0; n < 8; n++) {
m_imageList.Add(hIcon[n]);
}

static char* color[] = {"white", "black", "red",


"blue", "yellow", "cyan",
"purple", "green"};

cbi.mask = CBEIF_IMAGE|CBEIF_TEXT|CBEIF_OVERLAY|
CBEIF_SELECTEDIMAGE;

for (n = 0; n < 8; n++) {


cbi.iItem = n;
cbi.pszText = color[n];
cbi.iImage = n+6; //6 is the offset into the image list from
cbi.iSelectedImage = n+6; // the first six items we added...
cbi.iOverlay = n+6;
int nItem = pCombo->InsertItem(&cbi);
ASSERT(nItem == n);
}
The addition of the icons above is similar to the EX06B list control example in Chapter 6. The for loop fills out
the COMBOBOXEXITEM structure and then calls CComboBoxEx::InsertItem with each item to add it to the list.
Add a handler for the second extended combo box. The second extended combo box handler is essentially the
same as the first:
void CDialog1::OnSelchangeComboboxex2()
{
COMBOBOXEXITEM cbi;
CString str ("dummy_string");
- 125 -

CComboBoxEx * pCombo = (CComboBoxEx *)GetDlgItem(IDC_COMBOBOXEX2);


int nSel = pCombo->GetCurSel();
cbi.iItem = nSel;
cbi.pszText = (LPTSTR)(LPCTSTR)str;
cbi.mask = CBEIF_TEXT;
cbi.cchTextMax = str.GetLength();
pCombo->GetItem(&cbi);
SetDlgItemText(IDC_STATIC8,str);

return;
}
Connect the view and the dialog. Add code to the virtual OnDraw function in ex09aView.cpp. The following
boldface code replaces the previous code:
void CEx09aView::OnDraw(CDC* pDC)
{
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
Use ClassWizard to add the OnLButtonDown member function to the CEx09aView class. Edit the AppWizard-
generated code as follows:
void CEx09aView::OnLButtonDown(UINT nFlags, CPoint point)
{
CDialog1 dlg;
dlg.DoModal();
}
Add a statement to include Dialog1.h in file ex09aView.cpp.
Compile and run the program. Now you can experiment with the various IE4 common controls to see how they
work and how you can apply them in your own applications.
Chapter Ten
66. Win32 Memory Management
Forget everything you ever knew about Win16 memory management. Some of the Win16 memory management
functions, such as GlobalAlloc, were carried forward into Win32, but this was done to enable developers to port
source code quickly. Underneath, the original functions work very differently, and many new ones have been added.
This chapter starts out with a dose of Win32 memory management theory, which includes coverage of the
fundamental heap management functions. Then you'll see how the C++ new and delete operators connect with the
underlying heap functions. Finally, you'll learn how to use the memory-mapped file functions, and you'll get some
practical tips on managing dynamic memory. In no way is this chapter intended to be a definitive description of
Win32 memory management. For that, you'll have to read Jeffrey Richter's Advanced Windows (Microsoft Press,
1997). (Be sure you have the latest edition—a new version may be in the works that covers Microsoft Windows
98/NT 5.0.)

At the time this edition was written, both Windows 98 and Windows NT 5.0 were in beta and not
released. Our examination of these betas indicates that the memory management has not changed
significantly.
67. Processes and Memory Space
Before you learn how Microsoft Windows manages memory, you must first understand what a process is. If you
already know what a program is, you're on your way. A program is an EXE file that you can launch in various ways in
Windows. Once a program is running, it's called a process. A process owns its memory, file handles, and other
system resources. If you launch the same program twice in a row, you have two separate processes running
simultaneously. Both the Microsoft Windows NT Task Manager (right-click the taskbar) and the Microsoft Windows
95 PVIEW95 program give you a detailed list of processes that are currently running, and they allow you to kill
processes that are not responding. The SPYXX program shows the relationships among processes, tasks, and
windows.

The Windows taskbar shows main windows, not processes. A single process (such as Windows
Explorer) might have several main windows, each supported by its own thread, and some processes
don't have windows at all. (See Chapter 12 for a discussion of threads.)
- 126 -

The important thing to know about a process is that it has its own "private" 4-gigabyte (GB) virtual address space
(which I'll describe in detail in the next section). For now, pretend that your computer has hundreds of gigabytes of
RAM and that each process gets 4 GB. Your program can access any byte of this space with a single 32-bit linear
address. Each process's memory space contains a variety of items, including the following:
Your program's EXE image
Any nonsystem DLLs that your program loads, including the MFC DLLs
Your program's global data (read-only as well as read/write)
Your program's stack
Dynamically allocated memory, including Windows and C runtime library (CRT) heaps
Memory-mapped files
Interprocess shared memory blocks
Memory local to specific executing threads
All sorts of special system memory blocks, including virtual memory tables
The Windows kernel and executive, plus DLLs that are part of Windows
67.1 The Windows 95 Process Address Space
In Windows 95, only the bottom 2 GB (0 to 0x7FFFFFFF) of address space is truly private, and the bottom 4 MB of
that is off-limits. The stack, heaps, and read/write global memory are mapped in the bottom 2 GB along with
application EXE and DLL files.
The top 2 GB of space is the same for all processes and is shared by all processes. The Windows 95 kernel,
executive, virtual device drivers (VxDs), and file system code, along with important tables such as page tables, are
mapped to the top 1 GB (0xC0000000 to 0xFFFFFFFF) of address space. Windows DLLs and memory-mapped files
are located in the range 0x80000000 to 0xBFFFFFFF. Figure 10-1 shows a memory map of two processes using the
same program.

Figure 10-1. A typical Windows 95 virtual memory map for two processes linked to the same EXE file.
- 127 -

How safe is all this? It's next to impossible for one process to overwrite another process's stack, global, or heap
memory because this memory, located in the bottom 2 GB of virtual address space, is assigned only to that specific
process. All EXE and DLL code is flagged as read-only, so there's no problem if the code is mapped in several
processes.
However, because important Windows read/write data is mapped there, the top 1 GB of address space is vulnerable.
An errant program could wipe out important system tables located in this region. In addition, one process could mess
up another process's memory-mapped files in the range 0x80000000 through 0xBFFFFFFF because this region is
shared by all processes.
67.2 The Windows NT Process Address Space
A process in Windows NT can access only the bottom 2 GB of its address space, and the lowest and highest 64 KB
of that is inaccessible. The EXE, the application's DLLs and Windows DLLs, and memory-mapped files all reside in
this space between 0x00010000 and 0x7FFEFFFF. The Windows NT kernel, executive, and device drivers all reside
in the upper 2 GB, where they are completely protected from any tampering by an errant program. Memory-mapped
files are safer, too. One process cannot access another's memory-mapped file without knowing the file's name and
explicitly mapping a view.
68. How Virtual Memory Works
You know that your computer doesn't really have hundreds of gigabytes of RAM. And it doesn't have hundreds of
gigabytes of disk space either. Windows uses some smoke and mirrors here.
First of all, a process's 4-GB address space is going to be used sparsely. Various programs and data elements will
be scattered throughout the 4-GB address space in 4-KB units starting on 4-KB boundaries. Each 4-KB unit, called a
page, can hold either code or data. When a page is being used, it occupies physical memory, but you never see its
physical memory address. The Intel microprocessor chip efficiently maps a 32-bit virtual address to both a physical
page and an offset within the page, using two levels of 4-KB page tables, as shown in Figure 10-2. Note that
individual pages can be flagged as either read-only or read/write. Also note that each process has its own set of
page tables. The chip's CR3 register holds a pointer to the directory page, so when Windows switches from one
process to another, it simply updates CR3.

Figure 10-2. Win32 virtual memory management (Intel).


So now our process is down from 4 GB to maybe 5 MB—a definite improvement. But if we're running several
programs, along with Windows itself, we'll still run out of RAM. If you look at Figure 10-2 again, you'll notice that the
page table entry has a "present" bit that indicates whether the 4-KB page is currently in RAM. If we try to access a
page that's not in RAM, an interrupt fires and Windows analyzes the situation by checking its internal tables. If the
memory reference was bogus, we'll get the dreaded "page fault" message and the program will exit. Otherwise,
Windows reads the page from a disk file into RAM and updates the page table by loading the physical address and
setting the present bit. This is the essence of Win32 virtual memory.
The Windows virtual memory manager figures out how to read and write 4-KB pages so that it optimizes
performance. If one process hasn't used a page for a while and another process needs memory, the first page is
swapped out or discarded and the RAM is used for the new process's page. Your program isn't normally aware that
- 128 -

this is going on. The more disk I/O that happens, however, the worse your program's performance will be, so it
stands to reason that more RAM is better.
I mentioned the word "disk," but I haven't talked about files yet. All processes share a big systemwide swap file that's
used for all read/write data and some read-only data. (Windows NT supports multiple swap files.) Windows
determines the swap file size based on available RAM and free disk space, but there are ways to fine-tune the swap
file's size and specify its physical location on disk.
The swap file isn't the only file used by the virtual memory manager, however. It wouldn't make sense to write code
pages back to the swap file, so instead of using the swap file, Windows maps EXE and DLL files directly to their files
on disk. Because the code pages are marked read-only, there's never a need to write them back to disk.
If two processes use the same EXE file, that file is mapped into each process's address space. The code and
constants never change during program execution, so the same physical memory can be mapped for each process.
The two processes cannot share global data, however, and Windows 95 and Windows NT handle this situation
differently. Windows 95 maps separate copies of the global data to each process. In Windows NT, both processes
use the same copy of each page of global data until one process attempts to write to that page. At that point the
page is copied; as a result, each process has its own private copy stored at the same virtual address.

A dynamic link library can be mapped directly to its DLL file only if the DLL can be loaded at its
designated base address. If a DLL were statically linked to load at, say, 0x10000000 but that
address range is already occupied by another DLL, Windows must "fix up" the addresses within the
DLL code. Windows NT copies the altered pages to the swap file when the DLL is first loaded, but
Windows 95 can do the fixup "on the fly" when the pages are brought into RAM. Needless to say, it's
important to build your DLLs with nonoverlapping address ranges. If you're using the MFC DLLs, set
the base address of your own DLLs outside the range 0x5F400000 through 0x5FFFFFFF. Chapter
22 provides more details on writing DLLs.
Memory-mapped files, which I'll talk about later, are also mapped directly. These can be flagged as read/write and
made available for sharing among processes.
For Win32 Programmers: Segment Registers in Win32
If you've experimented with the debugger in Win32, you may have noticed the segment registers,
particularly CS, DS, and SS. These 16-bit relics haven't gone away, but you can mostly ignore them.
In 32-bit mode, the Intel microprocessor still uses segment registers, which are 16 bits long, to
translate addresses prior to sending them through the virtual memory system. A table in RAM, called
the descriptor table, has entries that contain the virtual memory base address and block size for
code, data, and stack segments. In 32-bit mode, these segments can be up to 4 GB in size and can
be flagged as read-only or read/write. For every memory reference, the chip uses the selector, the
contents of a segment register, to look up the descriptor table entry for the purpose of translating the
address.
Under Win32, each process has two segments—one for code and one for data and the stack. You
can assume that both have a base value of 0 and a size of 4 GB, so they overlap. The net result is
no translation at all, but Windows uses some tricks that exclude the bottom 16 KB from the data
segment. If you try to access memory down there, you get a protection fault instead of a page fault,
which is useful for debugging null pointers.
Some future operating system might someday use segments to get around that annoying 4-GB size
limitation, but by then we'll have Win64 to worry about!
69. The VirtualAlloc Function—Committed and Reserved Memory
If your program needs dynamic memory, sooner or later the Win32 VirtualAlloc function will be called. Chances are
that your program will never call VirtualAlloc; instead you'll rely on the Windows heap or the CRT heap functions to
call it directly. Knowing how VirtualAlloc works, however, will help you better understand the functions that call it.
First you must know the meanings of reserved and committed memory. When memory is reserved, a contiguous
virtual address range is set aside. If, for example, you know that your program is going to use a single 5-MB memory
block (known as a region) but you don't need to use it all right away, you call VirtualAlloc with a MEM_RESERVE
allocation type parameter and a 5-MB size parameter. Windows rounds the start address of the region to a 64-KB
boundary and prevents your process from reserving other memory in the same range. You can specify a start
address for your region, but more often you'll let Windows assign it for you. Nothing else happens. No RAM is
allocated, and no swap file space is set aside.
When you get more serious about needing memory, you call VirtualAlloc again to commit the reserved memory,
using a MEM_COMMIT allocation type parameter. Now the start and end addresses of the region are rounded to 4-
KB boundaries, and corresponding swap file pages are set aside together with the required page table. The block is
designated either read-only or read/write. Still no RAM is allocated, however; RAM allocation occurs only when you
- 129 -

try to access the memory. If the memory was not previously reserved, no problem. If the memory was previously
committed, still no problem. The rule is that memory must be committed before you can use it.
You call the VirtualFree function to "decommit" committed memory, thereby returning the designated pages back to
reserved status. VirtualFree can also free a reserved region of memory, but you have to specify the base address
you got from a previous VirtualAlloc reservation call.
70. The Windows Heap and the GlobalAlloc Function Family
A heap is a memory pool for a specific process. When your program needs a block of memory, it calls a heap
allocation function, and it calls a companion function to free the memory. There's no assumption about 4-KB page
boundaries; the heap manager uses space in existing pages or calls VirtualAlloc to get more pages. First we'll look at
Windows heaps. Next we'll consider heaps managed by the CRT library for functions like malloc and new.
Windows provides each process with a default heap, and the process can create any number of additional Windows
heaps. The HeapAlloc function allocates memory in a Windows heap, and HeapFree releases it.
You might never need to call HeapAlloc yourself, but it will be called for you by the GlobalAlloc function that's left
over from Win16. In the ideal 32-bit world, you wouldn't have to use GlobalAlloc, but in this real world, we're stuck
with a lot of code ported from Win16 that uses "memory handle" (HGLOBAL) parameters instead of 32-bit memory
addresses.
GlobalAlloc uses the default Windows heap. It does two different things, depending on its attribute parameter. If you
specify GMEM_FIXED, GlobalAlloc simply calls HeapAlloc and returns the address cast as a 32-bit HGLOBAL value.
If you specify GMEM_MOVEABLE, the returned HGLOBAL value is a pointer to a handle table entry in your process.
That entry contains a pointer to the actual memory, which is allocated with HeapAlloc.
Why bother with "moveable" memory if it adds an extra level of indirection? You're looking at an artifact from Win16,
in which, once upon a time, the operating system actually moved memory blocks around. In Win32, moveable blocks
exist only to support the GlobalReAlloc function, which allocates a new memory block, copies bytes from the old
block to the new, frees the old block, and assigns the new block address to the existing handle table entry. If nobody
called GlobalReAlloc, we could always use HeapAlloc instead of GlobalAlloc.
Unfortunately, many library functions use HGLOBAL return values and parameters instead of memory addresses. If
such a function returns an HGLOBAL value, you should assume that memory was allocated with the
GMEM_MOVEABLE attribute, and that means you must call the GlobalLock function to get the memory address. (If
the memory was fixed, the GlobalLock call just returns the handle as an address.) Call GlobalUnlock when you're
finished accessing the memory. If you're required to supply an HGLOBAL parameter, to be absolutely safe you
should generate it with a GlobalAlloc(GMEM_MOVEABLE, …) call in case the called function decides to call
GlobalReAlloc and expects the handle value to be unchanged.
71. The Small-Block Heap, the C++ new and delete Operators, and _heapmin
You can use the Windows HeapAlloc function in your programs, but you're more likely to use the malloc and free
functions supplied by the CRT. If you write C++ code, you won't call these functions directly; instead, you'll use the
new and delete operators, which map directly to malloc and free. If you use new to allocate a block larger than a
certain threshold (480 bytes is the default), the CRT passes the call straight through to HeapAlloc to allocate memory
from a Windows heap created for the CRT. For blocks smaller than the threshold, the CRT manages a small-block
heap, calling VirtualAlloc and VirtualFree as necessary. Here is the algorithm:
Memory is reserved in 4-MB regions.
Memory is committed in 64-KB blocks (16 pages).
Memory is decommitted 64 KB at a time. As 128 KB becomes free, the last 64 KB is decommitted.
A 4-MB region is released when every page in that region has been decommitted.
As you can see, this small-block heap takes care of its own cleanup. The CRT's Windows heap doesn't automatically
decommit and unreserve pages, however. To clean up the larger blocks, you must call the CRT _heapmin function,
which calls the windows HeapCompact function. (Unfortunately, the Windows 95 version of HeapCompact doesn't do
anything—all the more reason to use Windows NT.) Once pages are decommitted, other programs can reuse the
corresponding swap file space.

In previous versions of the CRT, the free list pointers were stored inside the heap pages. This
strategy required the malloc function to "touch" (read from the swap file) many pages to find free
space, and this degraded performance. The current system, which stores the free list in a separate
area of memory, is faster and minimizes the need for third-party heap management software.
If you want to change or access the block size threshold, use the CRT functions _set_sbh_threshold and
_get_sbh_threshold.
- 130 -

A special debug version of malloc, _malloc_dbg, adds debugging information inside allocated memory blocks. The
new operator calls _malloc_dbg when you build an MFC project with _DEBUG defined. Your program can then
detect memory blocks that you forgot to free or that you inadvertently overwrote.
72. Memory-Mapped Files
In case you think you don't have enough memory management options already, I'll toss you another one. Suppose
your program needs to read a DIB (device-independent bitmap) file. Your instinct would be to allocate a buffer of the
correct size, open the file, and then call a read function to copy the whole disk file into the buffer. The Windows
memory-mapped file is a more elegant tool for handling this problem, however. You simply map an address range
directly to the file. When the process accesses a memory page, Windows allocates RAM and reads the data from
disk. Here's what the code looks like:
HANDLE hFile = ::CreateFile(strPathname, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
ASSERT(hFile != NULL);
HANDLE hMap = ::CreateFileMapping(hFile, NULL, PAGE_READONLY,
0, 0, NULL);
ASSERT(hMap != NULL);
LPVOID lpvFile = ::MapViewOfFile(hMap, FILE_MAP_READ,
0, 0, 0); // Map whole file
DWORD dwFileSize = ::GetFileSize(hFile, NULL); // useful info
// Use the file
::UnmapViewOfFile(lpvFile);
::CloseHandle(hMap);
::CloseHandle(hFile);
Here you're using virtual memory backed by the DIB file. Windows determines the file size, reserves a corresponding
address range, and commits the file's storage as the physical storage for this range. In this case, lpvFile is the start
address. The hMap variable contains the handle for the file mapping object, which can be shared among processes
if desired.
The DIB in the example above is a small file that you could read entirely into a buffer. Imagine a larger file for which
you would normally issue seek commands. A memory-mapped file works for such a file, too, because of the
underlying virtual memory system. RAM is allocated and pages are read when you access them, and not before.

By default, the entire file is committed when you map it, although it's possible to map only part of a
file.
If two processes share a file mapping object (such as hMap in the sample code above), the file itself is, in effect,
shared memory, but the virtual addresses returned by MapViewOfFile might be different. Indeed, this is the preferred
Win32 method of sharing memory. (Calling the GlobalAlloc function with the GMEM_SHARE flag doesn't create
shared memory as it did in Win16.) If memory sharing is all you want to do and you don't need a permanent disk file,
you can omit the call to CreateFile and pass 0xFFFFFFFF as the CreateFileMapping hFile parameter. Now the
shared memory will be backed by pages in the swap file. Consult Richter for details on memory-mapped files. The
EX35B and EX35C sample programs in Chapter 35 illustrate sharing of memory-mapped files.

If you intend to access only a few random pages of a file mapping object that is backed by the swap
file, you can use a technique that Jeffrey Richter describes in Advanced Windows under the heading
"Sparsely Committed Memory-Mapped Files." In this case, you call CreateFileMapping with a special
flag and then you commit specific address ranges later with the VirtualAlloc function.

You might want to look carefully at the Windows message WM_COPYDATA. This message lets you
transfer data between processes in shared memory without having to deal with the file mapping API.
You must send this message rather than post it, which means the sending process has to wait while
the receiving process copies and processes the data.
Unfortunately, there's no direct support for memory-mapped files or shared memory in MFC. The CSharedFile class
supports only clipboard memory transfers using HGLOBAL handles, so the class isn't as useful as its name implies.
73. Accessing Resources
Resources are contained inside EXEs and DLLs and thus occupy virtual address space that doesn't change during
the life of the process. This fact makes it easy to read a resource directly. If you need to access a bitmap, for
example, you can get the DIB address with code like this:
LPVOID lpvResource = (LPVOID) ::LoadResource(NULL,
- 131 -

::FindResource(NULL, MAKEINTRESOURCE(IDB_REDBLOCKS),
RT_BITMAP));
The LoadResource function returns an HGLOBAL value, but you can safely cast it to a pointer.
74. Some Tips for Managing Dynamic Memory
The more you use the heap, the more fragmented it gets and the more slowly your program runs. If your program is
supposed to run for hours or days at a time, you have to be careful. It's better to allocate all the memory you need
when your program starts and then free it when the program exits, but that's not always possible. The CString class
is a nuisance because it's constantly allocating and freeing little bits of memory. Fortunately, MFC developers have
recently made some improvements.
Don't forget to call _heapmin every once in a while if your program allocates blocks larger than the small-block heap
threshold. And be careful to remember where heap memory comes from. You'd have a big problem, for instance, if
you called HeapFree on a small-block pointer you got from new.
Be aware that your stack can be as big as it needs to be. Because you no longer have a 64-KB size limit, you can
put large objects on the stack, thereby reducing the need for heap allocations.
As in Win16, your program doesn't run at full speed and then suddenly throw an exception when Windows runs out
of swap space. Your program just slowly grinds to a halt, making your customer unhappy. And there's not much you
can do except try to figure out which program is eating memory and why. Because the Windows 95 USER and GDI
modules still have 16-bit components, there is some possibility of exhausting the 64-KB heaps that hold GDI objects
and window structures. This possibility is pretty remote, however, and if it happens, it probably indicates a bug in
your program.
75. Optimizing Storage for Constant Data
Remember that the code in your program is backed not by the swap file but directly by its EXE and DLL files. If
several instances of your program are running, the same EXE and DLL files will be mapped to each process's virtual
address space. What about constant data? You would want that data to be part of the program rather than have it
copied to another block of address space that's backed by the swap file.
You've got to work a little bit to ensure that constant data gets stored with the program. First consider string
constants, which often permeate your programs. You would think that these would be read-only data, but guess
again. Because you're allowed to write code like this:
char* pch = "test";
*pch = `x';
"test" can't possibly be constant data, and it isn't.
If you want "test" to be a constant, you must declare it as an initialized const static or global variable. Here's the
global definition:
const char g_pch[] = "test";
Now g_pch is stored with the code, but where, specifically? To answer that, you must understand the "data sections"
that the Visual C++ linker generates. If you set the link options to generate a map file, you'll see a long list of the
sections (memory blocks) in your program. Individual sections can be designated for code or data, and they can be
read-only or read/write. The important sections and their characteristics are listed here.
Name Type Access Contents

.text Code Read-only Program code

.rdata Data Read-only Constant initialized data

.data Data Read/write Nonconstant initialized data

.bss Data Read/write Nonconstant uninitialized data


The .rdata section is part of the EXE file, and that's where the linker puts the g_pch variable. The more stuff you put
in the .rdata section, the better. The use of the const modifier does the trick.
You can put built-in types and even structures in the .rdata section, but you can't put C++ objects there if they have
constructors. If you write a statement like the following one:
const CRect g_rect(0, 0, 100, 100);
the linker puts the object into the .bss section, and it will be backed separately to the swap file for each process. If
you think about it, this makes sense because the compiler must invoke the constructor function after the program is
loaded.
Now suppose you wanted to do the worst possible thing. You'd declare a CString global variable (or static class data
member) like this:
const CString g_str("this is the worst thing I can do");
- 132 -

Now you've got the CString object (which is quite small) in the .bss section, and you've also got a character array in
the .data section, neither of which can be backed by the EXE file. To make matters worse, when the program starts,
the CString class must allocate heap memory for a copy of the characters. You would be much better off using a
const character array instead of a CString object.
Chapter Eleven
76. Bitmaps
Without graphics images, Microsoft Windows-based applications would be pretty dull. Some applications depend on
images for their usefulness, but any application can be spruced up with the addition of decorative clip art from a
variety of sources. Windows bitmaps are arrays of bits mapped to display pixels. That might sound simple, but you
have to learn a lot about bitmaps before you can use them to create professional applications for Windows.
This chapter starts with the "old" way of programming bitmaps—creating the device-dependent GDI bitmaps that
work with a memory device context. You need to know these techniques because many programmers are still using
them and you'll also need to use them on occasion.
Next you'll graduate to the modern way of programming bitmaps—creating device-independent bitmaps (DIBs). If
you use DIBs, you'll have an easier time with colors and with the printer. In some cases you'll get better performance.
The Win32 function CreateDIBSection gives you the benefits of DIBs combined with all the features of GDI bitmaps.
Finally, you'll learn how to use the MFC CBitmapButton class to put bitmaps on pushbuttons. (Using CBitmapButton
to put bitmaps on pushbuttons has nothing to do with DIBs, but it's a useful technique that would be difficult to master
without an example.)
77. GDI Bitmaps and Device-Independent Bitmaps
There are two kinds of Windows bitmaps: GDI bitmaps and DIBs. GDI bitmap objects are represented by the
Microsoft Foundation Class (MFC) Library version 6.0 CBitmap class. The GDI bitmap object has an associated
Windows data structure, maintained inside the Windows GDI module, that is device-dependent. Your program can
get a copy of the bitmap data, but the bit arrangement depends on the display hardware. GDI bitmaps can be freely
transferred among programs on a single computer, but because of their device dependency, transferring bitmaps by
disk or modem doesn't make sense.

In Win32, you're allowed to put a GDI bitmap handle on the clipboard for transfer to another process,
but behind the scenes Windows converts the device-dependent bitmap to a DIB and copies the DIB
to shared memory. That's a good reason to consider using DIBs from the start.
DIBs offer many programming advantages over GDI bitmaps. Because a DIB carries its own color information, color
palette management is easier. DIBs also make it easy to control gray shades when printing. Any computer running
Windows can process DIBs, which are usually stored in BMP disk files or as a resource in your program's EXE or
DLL file. The wallpaper background on your monitor is read from a BMP file when you start Windows. The primary
storage format for Microsoft Paint is the BMP file, and Visual C++ uses BMP files for toolbar buttons and other
images. Other graphic interchange formats are available, such as TIFF, GIF, and JPEG, but only the DIB format is
directly supported by the Win32 API.
77.1 Color Bitmaps and Monochrome Bitmaps
Now might be a good time to reread the "Windows Color Mapping" section in Chapter 5. As you'll see in this chapter,
Windows deals with color bitmaps a little differently from the way it deals with brush colors.
Many color bitmaps are 16-color. A standard VGA board has four contiguous color planes, with 1 corresponding bit
from each plane combining to represent a pixel. The 4-bit color values are set when the bitmap is created. With a
standard VGA board, bitmap colors are limited to the standard 16 colors. Windows does not use dithered colors in
bitmaps.
A monochrome bitmap has only one plane. Each pixel is represented by a single bit that is either off (0) or on (1).
The CDC::SetTextColor function sets the "off" display color, and SetBkColor sets the "on" color. You can specify
these pure colors individually with the Windows RGB macro.
78. Using GDI Bitmaps
A GDI bitmap is simply another GDI object, such as a pen or a font. You must somehow create a bitmap, and then
you must select it into a device context. When you're finished with the object, you must deselect it and delete it. You
know the drill.
There's a catch, though, because the "bitmap" of the display or printer device is effectively the display surface or the
printed page itself. Therefore, you can't select a bitmap into a display device context or a printer device context. You
have to create a special memory device context for your bitmaps, using the CDC::CreateCompatibleDC function.
You must then use the CDC member function StretchBlt or BitBlt to copy the bits from the memory device context to
the "real" device context. These "bit-blitting" functions are generally called in your view class's OnDraw function. Of
course, you mustn't forget to clean up the memory device context when you're finished.
78.1 Loading a GDI Bitmap from a Resource
- 133 -

The easiest way to use a bitmap is to load it from a resource. If you look in ResourceView in the Workspace window,
you'll find a list of the project's bitmap resources. If you select a bitmap and examine its properties, you'll see a
filename.
Here's an example entry in an RC (resource script) file, when viewed by a text editor:
IDB_REDBLOCKS BITMAP DISCARDABLE "res\\Red Blocks.bmp"
IDB_REDBLOCKS is the resource ID, and the file is Red Blocks.bmp in the project's \res subdirectory. (This is one of
the Microsoft Windows 95 wallpaper bitmaps, normally located in the \WINDOWS directory.) The resource compiler
reads the DIB from disk and stores it in the project's RES file. The linker copies the DIB into the program's EXE file.
You know that the Red Blocks bitmap must be in device-independent format because the EXE can be run with any
display board that Windows supports.
The CDC::LoadBitmap function converts a resource-based DIB to a GDI bitmap. Below is the simplest possible self-
contained OnDraw function that displays the Red Blocks bitmap:
CMyView::OnDraw(CDC* pDC)
{
CBitmap bitmap; // Sequence is important
CDC dcMemory;
bitmap.LoadBitmap(IDB_REDBLOCKS);
dcMemory.CreateCompatibleDC(pDC);
dcMemory.SelectObject(&bitmap);
pDC->BitBlt(100, 100, 54, 96, &dcMemory, 0, 0, SRCCOPY);
// CDC destructor deletes dcMemory; bitmap is deselected
// CBitmap destructor deletes bitmap
}
The BitBlt function copies the Red Blocks pixels from the memory device context to the display (or printer) device
context. The bitmap is 54 bits wide by 96 bits high, and on a VGA display it occupies a rectangle of 54-by-96 logical
units, offset 100 units down and to the right of the upper-left corner of the window's client area.

The code above works fine for the display. As you'll see in Chapter 19, the application framework
calls the OnDraw function for printing, in which case pDC points to a printer device context. The
bitmap here, unfortunately, is configured specifically for the display and thus cannot be selected into
the printer-compatible memory device context. If you want to print a bitmap, you should look at the
CDib class described later in this chapter.
78.2 The Effect of the Display Mapping Mode
If the display mapping mode in the Red Blocks example is MM_TEXT, each bitmap pixel maps to a display pixel and
the bitmap fits perfectly. If the mapping mode is MM_LOENGLISH, the bitmap size is 0.54-by-0.96 inch, or 52-by-92
pixels for Windows 95, and the GDI must do some bit crunching to make the bitmap fit. Consequently, the bitmap
might not look as good with the MM_LOENGLISH mapping mode. Calling CDC::SetStretchBltMode with a parameter
value of COLORONCOLOR will make shrunken bitmaps look nicer.
78.3 Stretching the Bits
What if we want Red Blocks to occupy a rectangle of exactly 54-by-96 pixels, even though the mapping mode is not
MM_TEXT? The StretchBlt function is the solution. If we replace the BitBlt call with the following three statements,
Red Blocks is displayed cleanly, whatever the mapping mode:
CSize size(54, 96);
pDC->DPtoLP(&size);
pDC->StretchBlt(0, 0, size.cx, -size.cy,
&dcMemory, 0, 0, 54, 96, SRCCOPY);
With either BitBlt or StretchBlt, the display update is slow if the GDI has to actually stretch or compress bits. If, as in
the case above, the GDI determines that no conversion is necessary, the update is fast.
78.4 The EX11A Example
The EX11A example displays a resource-based bitmap in a scrolling view with mapping mode set to
MM_LOENGLISH. The program uses the StretchBlt logic described above, except that the memory device context
and the bitmap are created in the view's OnInitialUpdate member function and last for the life of the program. Also,
the program reads the bitmap size through a call to the CGdiObject member function GetObject, so it's not using
hard-coded values as in the preceding examples.
Here are the steps for building the example:
- 134 -

Run AppWizard to produce \vcpp32\ex11a\ex11a. Accept all the default settings but two: select Single
Document, and select the CScrollView view base class, as shown in Chapter 4, for CEx11aView. The options
and the default class names are shown here.

Import the Gold Weave bitmap. Choose Resource from Visual C++'s Insert menu. Import the bitmap Gold
Weave.bmp from the \WINDOWS directory. (If your version of Windows doesn't have this bitmap, load it from
this book's companion CD-ROM.) Visual C++ will copy this bitmap file into your project's \res subdirectory.
Assign the ID IDB_GOLDWEAVE, and save the changes.
Add the following private data members to the class CEx11aView. Edit the file ex11aView.h or use ClassView.
The bitmap and the memory device context last for the life of the view. The CSize objects are the source
(bitmap) dimensions and the destination (display) dimensions.
CDC* m_pdcMemory;
CBitmap* m_pBitmap;
CSize m_sizeSource, m_sizeDest;
Edit the following member functions in the class CEx11aView. Edit the file ex11aView.cpp. The constructor and
destructor do C++ housekeeping for the embedded objects. You want to keep the constructor as simple as
possible because failing constructors cause problems. The OnInitialUpdate function sets up the memory device
context and the bitmap, and it computes output dimensions that map each bit to a pixel. The OnDraw function
calls StretchBlt twice—once by using the special computed dimensions and once by mapping each bit to a
0.01-by-0.01-inch square. Add the following boldface code:
CEx11aView::CEx11aView()
{
m_pdcMemory = new CDC;
m_pBitmap = new CBitmap;
}

CEx11aView::~CEx11aView()
{
// cleans up the memory device context and the bitmap
delete m_pdcMemory; // deselects bitmap
delete m_pBitmap;
}
- 135 -

void CEx11aView::OnDraw(CDC* pDC)


{
pDC->SetStretchBltMode(COLORONCOLOR);
pDC->StretchBlt(20, -20, m_sizeDest.cx, -m_sizeDest.cy,
m_pdcMemory, 0, 0,
m_sizeSource.cx, m_sizeSource.cy, SRCCOPY);

pDC->StretchBlt(350, -20, m_sizeSource.cx, -m_sizeSource.cy,


m_pdcMemory, 0, 0,
m_sizeSource.cx, m_sizeSource.cy, SRCCOPY);
}

void CEx11aView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(800, 1050); // 8-by-10.5 inches
CSize sizeLine = CSize(sizeTotal.cx / 100, sizeTotal.cy / 100);
SetScrollSizes(MM_LOENGLISH, sizeTotal, sizeTotal, sizeLine);

BITMAP bm; // Windows BITMAP data structure; see Win32 help


if (m_pdcMemory->GetSafeHdc() == NULL) {
CClientDC dc(this);
OnPrepareDC(&dc); // necessary
m_pBitmap->LoadBitmap(IDB_GOLDWEAVE);
m_pdcMemory->CreateCompatibleDC(&dc);
m_pdcMemory->SelectObject(m_pBitmap);
m_pBitmap->GetObject(sizeof(bm), &bm);
m_sizeSource.cx = bm.bmWidth;
m_sizeSource.cy = bm.bmHeight;
m_sizeDest = m_sizeSource;
dc.DPtoLP(&m_sizeDest);
}
}
Build and test the EX11A application. Your screen should look like this.

Try the Print Preview and Print features. The bitmap prints to scale because the application framework applies
the MM_LOENGLISH mapping mode to the printer device context just as it does to the display device context.
The output looks great in Print Preview mode, but (depending on your print drivers) the printed output will
probably be either blank or microscopic! We'll fix that soon.
- 136 -

79. Using Bitmaps to Improve the Screen Display


You've seen an example program that displays a bitmap that originated outside the program. Now you'll see an
example program that generates its own bitmap to support smooth motion on the screen. The principle is simple: you
draw on a memory device context with a bitmap selected, and then you zap the bitmap onto the screen.
79.1 The EX11B Example
In the EX05C example in Chapter 5, the user dragged a circle with the mouse. As the circle moved, the display
flickered because the circle was erased and redrawn on every mouse-move message. EX11B uses a GDI bitmap to
correct this problem. The EX05C custom code for mouse message processing carries over almost intact; most of the
new code is in the OnPaint and OnInitialUpdate functions.
In summary, the EX11B OnInitialUpdate function creates a memory device context and a bitmap that are compatible
with the display. The OnPaint function prepares the memory device context for drawing, passes OnDraw a handle to
the memory device context, and copies the resulting bitmap from the memory device context to the display.
Here are the steps to build EX11B from scratch:
Run AppWizard to produce \vcpp32\ex11b\ex11b. Accept all the default settings but two: select Single
Document and select CScrollView view as the base class for CEx11bView. The options and the default class
names are shown here.

Use ClassWizard to add CEx11bView message handlers. Add message handlers for the following messages:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_MOUSEMOVE
WM_PAINT
Edit the ex11bView.h header file. Add the private data members shown here to the CEx11bView class:
private:
const CSize m_sizeEllipse;
CPoint m_pointTopLeft;
BOOL m_bCaptured;
CSize m_sizeOffset;
CDC* m_pdcMemory;
CBitmap* m_pBitmap;
- 137 -

Code the CEx11bView constructor and destructor in ex11bView.cpp. You need a memory device context object
and a bitmap GDI object. These are constructed in the view's constructor and destroyed in the view's
destructor. Add the following boldface code:
CEx11bView::CEx11bView() : m_sizeEllipse(100, -100),
m_pointTopLeft(10, -10),
m_sizeOffset(0, 0)
{
m_bCaptured = FALSE;
m_pdcMemory = new CDC;
m_pBitmap = new CBitmap;
}

CEx11bView::~CEx11bView()
{
delete m_pBitmap; // already deselected
delete m_pdcMemory;
}
Add code for the OnInitialUpdate function in ex11bView.cpp. The C++ memory device context and bitmap
objects are already constructed. This function creates the corresponding Windows objects. Both the device
context and the bitmap are compatible with the display context dc, but you must explicitly set the memory
device context's mapping mode to match the display context. You could create the bitmap in the OnPaint
function, but the program runs faster if you create it once here. Add the boldface code shown here:
void CEx11bView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(800, 1050); // 8-by-10.5 inches
CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2);
CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50);
SetScrollSizes(MM_LOENGLISH, sizeTotal, sizePage, sizeLine);
// creates the memory device context and the bitmap
if (m_pdcMemory->GetSafeHdc() == NULL) {
CClientDC dc(this);
OnPrepareDC(&dc);
CRect rectMax(0, 0, sizeTotal.cx, -sizeTotal.cy);
dc.LPtoDP(rectMax);
m_pdcMemory->CreateCompatibleDC(&dc);
// makes bitmap same size as display window
m_pBitmap->CreateCompatibleBitmap(&dc, rectMax.right,
rectMax.bottom);
m_pdcMemory->SetMapMode(MM_LOENGLISH);
}
}
Add code for the OnPaint function in ex11bView.cpp. Normally it isn't necessary to map the WM_PAINT
message in your derived view class. The CView version of OnPaint contains the following code:
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
In this example, you will be using the OnPaint function to reduce screen flicker through the use of a memory
device context. OnDraw is passed this memory device context for the display, and it is passed the printer
device context for printing. Thus, OnDraw can perform tasks common to the display and to the printer. You
don't need to use the bitmap with the printer because the printer has no speed constraint.
The OnPaint function must perform, in order, the following three steps to prepare the memory device context
for drawing:
Select the bitmap into the memory device context.
Transfer the invalid rectangle (as calculated by OnMouseMove) from the display context to the memory
device context. There is no SetClipRect function, but the CDC::IntersectClipRect function, when called after
- 138 -

the CDC::SelectClipRgn function (with a NULL parameter), has the same effect. If you don't set the clipping
rectangle to the minimum size, the program runs more slowly.
Initialize the bitmap to the current window background color. The CDC::PatBlt function fills the specified
rectangle with a pattern. In this case, the pattern is the brush pattern for the current window background.
That brush must first be constructed and selected into the memory device context.
After the memory device context is prepared, OnPaint can call OnDraw with a memory device context
parameter. Then the CDC::BitBlt function copies the updated rectangle from the memory device context to the
display device context. Add the following boldface code:
void CEx11bView::OnPaint()
{
CPaintDC dc(this); // device context for painting
OnPrepareDC(&dc);
CRect rectUpdate;
dc.GetClipBox(&rectUpdate);
CBitmap* pOldBitmap = m_pdcMemory->SelectObject(m_pBitmap);
m_pdcMemory->SelectClipRgn(NULL);
m_pdcMemory->IntersectClipRect(&rectUpdate);
CBrush backgroundBrush((COLORREF) ::GetSysColor(COLOR_WINDOW));
CBrush* pOldBrush = m_pdcMemory->SelectObject(&backgroundBrush);
m_pdcMemory->PatBlt(rectUpdate.left, rectUpdate.top,
rectUpdate.Width(), rectUpdate.Height(),
PATCOPY);
OnDraw(m_pdcMemory);
dc.BitBlt(rectUpdate.left, rectUpdate.top,
rectUpdate.Width(), rectUpdate.Height(),
m_pdcMemory, rectUpdate.left, rectUpdate.top,
SRCCOPY);
m_pdcMemory->SelectObject(pOldBitmap);
m_pdcMemory->SelectObject(pOldBrush);
}
Code the OnDraw function in ex11bView.cpp. Copy the code from ex05cView.cpp. In EX11B, OnDraw is
passed a pointer to a memory device context by the OnPaint function. For printing, OnDraw is passed a pointer
to the printer device context.
Copy the mouse message-handling code from ex05cView.cpp. Copy the functions shown below from
ex05cView.cpp to ex11bView.cpp. Be sure to change the functions' class names from CEx05cView to
CEx11bView.
OnLButtonDown
OnLButtonUp
OnMouseMove
Change two lines in the OnMouseMove function in ex11bView.cpp. Change the following two lines:
InvalidateRect(rectOld, TRUE);
InvalidateRect(rectNew, TRUE);
to
InvalidateRect(rectOld, FALSE);
InvalidateRect(rectNew, FALSE);
If the second CWnd::InvalidateRect parameter is TRUE (the default), Windows erases the background before
repainting the invalid rectangle. That's what you needed in EX05C, but the background erasure is what causes
the flicker. Because the entire invalid rectangle is being copied from the bitmap, you no longer need to erase
the background. The FALSE parameter prevents this erasure.
Build and run the application. Here is the EX11B program output.
- 139 -

Is the circle's movement smoother now? The problem is that the bitmap is only 8-by-10.5 inches, and if the
scrolling window is big enough, the circle goes off the edge. One solution to this problem is to make the bitmap
as big as the largest display.
79.2 Windows Animation
EX11B is a crude attempt at Windows animation. What if you wanted to move an angelfish instead of a circle? Win32
doesn't have an Angelfish function (yet), so you'd have to keep your angelfish in its own bitmap and use the
StretchBlt mask ROP codes to merge the angelfish with the background. You'd probably keep the background in its
own bitmap, too. These techniques are outside the scope of this book. If you are interested in learning more about
Windows Animation, run out and get Nigel Thompson's Animation Techniques in Win32 (Microsoft Press, 1995).
After you read it, you can get rich writing video games for Windows!
80. DIBs and the CDib Class
There's an MFC class for GDI bitmaps (CBitmap), but there's no MFC class for DIBs. Don't worry—I'm giving you
one here. It's a complete rewrite of the CDib class from the early editions of this book (prior to the fourth edition), and
it takes advantage of Win32 features such as memory-mapped files, improved memory management, and DIB
sections. It also includes palette support. Before you examine the CDib class, however, you need a little background
on DIBs.
80.1 A Few Words About Palette Programming
Windows palette programming is quite complex, but you've got to deal with it if you expect your users to run their
displays in the 8-bpp (bits per pixel) mode—and many users will if they have video cards with 1 MB or less of
memory.
Suppose you're displaying a single DIB in a window. First you must create a logical palette, a GDI object that
contains the colors in the DIB. Then you must "realize" this logical palette into the hardware system palette, a table of
the 256 colors the video card can display at that instant. If your program is the foreground program, the realization
process tries to copy all your colors into the system palette, but it doesn't touch the 20 standard Windows colors. For
the most part, your DIB looks just like you want it to look.
But what if another program is the foreground program, and what if that program has a forest scene DIB with 236
shades of green? Your program still realizes its palette, but something different happens this time. Now the system
palette won't change, but Windows sets up a new mapping between your logical palette and the system palette. If
your DIB contains a neon pink color, for example, Windows maps it to the standard red color. If your program forgot
to realize its palette, your neon pink stuff would turn green when the other program went active.
The forest scene example is extreme because we assumed that the other program grabbed 236 colors. If instead the
other program realized a logical palette with only 200 colors, Windows would let your program load 36 of its own
colors, including, one hopes, neon pink.
So when is a program supposed to realize its palette? The Windows message WM_PALETTECHANGED is sent to
your program's main window whenever a program, including yours, realizes its palette. Another message,
WM_QUERYNEWPALETTE, is sent whenever one of the windows in your program gets the input focus. Your
program should realize its palette in response to both these messages (unless your program generated the
message). These palette messages are not sent to your view window, however. You must map them in your
- 140 -

application's main frame window and then notify the view. Chapter 13 discusses the relationship between the frame
window and the view, and Chapter 26 contains a complete palette-aware MDI application (EX26A).
You call the Win32 RealizePalette function to perform the realization, but first you must call SelectPalette to select
your DIB's logical palette into the device context. SelectPalette has a flag parameter that you normally set to FALSE
in your WM_PALETTECHANGED and WM_QUERYNEWPALETTE handlers. This flag ensures that your palette is
realized as a foreground palette if your application is indeed running in the foreground. If you use a TRUE flag
parameter here, you can force Windows to realize the palette as though the application were in the background.
You must also call SelectPalette for each DIB that you display in your OnDraw function. This time you call it with a
TRUE flag parameter. Things do get complicated if you're displaying several DIBs, each with its own palette.
Basically, you've got to choose a palette for one of the DIBs and realize it (by selecting it with the FALSE parameter)
in the palette message handlers. The chosen DIB will end up looking better than the other DIBs. There are ways of
merging palettes, but it might be easier to go out and buy more video memory.
80.2 DIBs, Pixels, and Color Tables
A DIB contains a two-dimensional array of elements called pixels. In many cases, each DIB pixel will be mapped to a
display pixel, but the DIB pixel might be mapped to some logical area on the display, depending on the mapping
mode and the display function stretch parameters.
A pixel consists of 1, 4, 8, 16, 24, or 32 contiguous bits, depending on the color resolution of the DIB. For 16-bpp, 24-
bpp, and 32-bpp DIBs, each pixel represents an RGB color. A pixel in a 16-bpp DIB typically contains 5 bits each for
red, green, and blue values; a pixel in a 24-bpp DIB has 8 bits for each color value. The 16-bpp and 24-bpp DIBs are
optimized for video cards that can display 65,536 or 16.7 million simultaneous colors.
A 1-bpp DIB is a monochrome DIB, but these DIBs don't have to be black and white—they can contain any two
colors chosen from the color table that is built into each DIB. A monochrome bitmap has two 32-bit color table
entries, each containing 8 bits for red, green, and blue values plus another 8 bits for flags. Zero (0) pixels use the first
entry, and one (1) pixel uses the second. Whether you have a 65,536-color video card or a 16.7-million-color card,
Windows can display the two colors directly. (Windows truncates 8-bits-per-color values to 5 bits for 65,536-color
displays.) If your video card is running in 256-color palettized mode, your program can adjust the system palette to
load the two specified colors.
Eight-bpp DIBs are quite common. Like a monochrome DIB, an 8-bpp DIB has a color table, but the color table has
256 (or fewer) 32-bit entries. Each pixel is an index into this color table. If you have a palettized video card, your
program can create a logical palette from the 256 entries. If another program (running in the foreground) has control
of the system palette, Windows does its best to match your logical palette colors to the system palette.
What if you're trying to display a 24-bpp DIB with a 256-color palettized video card? If the DIB author was nice, he or
she included a color table containing the most important colors in the DIB. Your program can build a logical palette
from that table, and the DIB will look fine. If the DIB has no color table, use the palette returned by the Win32
CreateHalftonePalette function; it's better than the 20 standard colors you'd get with no palette at all. Another option
is to analyze the DIB to identify the most important colors, but you can buy a utility to do that.
80.3 The Structure of a DIB Within a BMP File
You know that the DIB is the standard Windows bitmap format and that a BMP file contains a DIB. So let's look
inside a BMP file to see what's there. Figure 11-1 shows a layout for a BMP file.
- 141 -

Figure 11-1. The layout for a BMP file.


The BITMAPFILEHEADER structure contains the offset to the image bits, which you can use to compute the
combined size of the BITMAPINFOHEADER structure and the color table that follows. The BITMAPFILEHEADER
structure contains a file size member, but you can't depend on it because you don't know whether the size is
measured in bytes, words, or double words.
The BITMAPINFOHEADER structure contains the bitmap dimensions, the bits per pixel, compression information for
both 4-bpp and 8-bpp bitmaps, and the number of color table entries. If the DIB is compressed, this header contains
the size of the pixel array; otherwise, you can compute the size from the dimensions and the bits per pixel.
Immediately following the header is the color table (if the DIB has a color table). The DIB image comes after that.
The DIB image consists of pixels arranged by column within rows, starting with the bottom row. Each row is padded
to a 4-byte boundary.
The only place you'll find a BITMAPFILEHEADER structure, however, is in a BMP file. If you get a DIB from the
clipboard, for example, there will not be a file header. You can always count on the color table to follow the
BITMAPINFOHEADER structure, but you can't count on the image to follow the color table. If you're using the
CreateDIBSection function, for example, you must allocate the bitmap info header and color table and then let
Windows allocate the image somewhere else.

This chapter and all the associated code are specific to Windows DIBs. There's also a well-
documented variation of the DIB format for OS/2. If you need to process these OS/2 DIBs, you'll
have to modify the CDib class.
80.4 DIB Access Functions
Windows supplies some important DIB access functions. None of these functions is wrapped by MFC, so you'll need
to refer to the online Win32 documentation for details. Here's a summary:
SetDIBitsToDevice—This function displays a DIB directly on the display or printer. No scaling occurs; one bitmap
bit corresponds to one display pixel or one printer dot. This scaling restriction limits the function's usefulness. The
function doesn't work like BitBlt because BitBlt uses logical coordinates.
StretchDIBits—This function displays a DIB directly on the display or printer in a manner similar to that of
StretchBlt.
GetDIBits—This function constructs a DIB from a GDI bitmap, using memory that you allocate. You have some
control over the format of the DIB because you can specify the number of color bits per pixel and the
compression. If you are using compression, you have to call GetDIBits twice—once to calculate the memory
needed and again to generate the DIB data.
CreateDIBitmap—This function creates a GDI bitmap from a DIB. As for all these DIB functions, you must supply
a device context pointer as a parameter. A display device context will do; you don't need a memory device
context.
- 142 -

CreateDIBSection—This Win32 function creates a special kind of DIB known as a DIB section. It then returns a
GDI bitmap handle. This function gives you the best features of DIBs and GDI bitmaps. You have direct access to
the DIB's memory, and with the bitmap handle and a memory device context, you can call GDI functions to draw
into the DIB.
80.5 The CDib Class
If DIBs look intimidating, don't worry. The CDib class makes DIB programming easy. The best way to get to know the
CDib class is to look at the public member functions and data members. Figure 11-2 shows the CDib header file.
Consult the ex11c folder on the companion CD-ROM to see the implementation code.
CDIB.H
#ifndef _INSIDE_VISUAL_CPP_CDIB
#define _INSIDE_VISUAL_CPP_CDIB

class CDib : public CObject


{
enum Alloc {noAlloc, crtAlloc,
heapAlloc}; // applies to BITMAPINFOHEADER
DECLARE_SERIAL(CDib)
public:
LPVOID m_lpvColorTable;
HBITMAP m_hBitmap;
LPBYTE m_lpImage; // starting address of DIB bits
LPBITMAPINFOHEADER m_lpBMIH; // buffer containing the
// BITMAPINFOHEADER
private:
HGLOBAL m_hGlobal; // for external windows we need to free;
// could be allocated by this class or
// allocated externally
Alloc m_nBmihAlloc;
Alloc m_nImageAlloc;
DWORD m_dwSizeImage; // of bits—not BITMAPINFOHEADER
// or BITMAPFILEHEADER
int m_nColorTableEntries;

HANDLE m_hFile;
HANDLE m_hMap;
LPVOID m_lpvFile;
HPALETTE m_hPalette;
public:
CDib();
CDib(CSize size, int nBitCount); // builds BITMAPINFOHEADER
~CDib();
int GetSizeImage() {return m_dwSizeImage;}
int GetSizeHeader()
{return sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * m_nColorTableEntries;}
CSize GetDimensions();
BOOL AttachMapFile(const char* strPathname, BOOL bShare = FALSE);
BOOL CopyToMapFile(const char* strPathname);
BOOL AttachMemory(LPVOID lpvMem, BOOL bMustDelete = FALSE,
HGLOBAL hGlobal = NULL);
BOOL Draw(CDC* pDC, CPoint origin,
CSize size); // until we implement CreateDibSection
HBITMAP CreateSection(CDC* pDC = NULL);
UINT UsePalette(CDC* pDC, BOOL bBackground = FALSE);
BOOL MakePalette();
- 143 -

BOOL SetSystemPalette(CDC* pDC);


BOOL Compress(CDC* pDC,
BOOL bCompress = TRUE); // FALSE means decompress
HBITMAP CreateBitmap(CDC* pDC);
BOOL Read(CFile* pFile);
BOOL ReadSection(CFile* pFile, CDC* pDC = NULL);
BOOL Write(CFile* pFile);
void Serialize(CArchive& ar);
void Empty();
private:
void DetachMapFile();
void ComputePaletteSize(int nBitCount);
void ComputeMetrics();
};
#endif // _INSIDE_VISUAL_CPP_CDIB
Figure 11-2. The CDib class declaration.
Here's a rundown of the CDib member functions, starting with the constructors and the destructor:
Default constructor—You'll use the default constructor in preparation for loading a DIB from a file or for attaching
to a DIB in memory. The default constructor creates an empty DIB object.
DIB section constructor—If you need a DIB section that is created by the CreateDIBSection function, use this
constructor. Its parameters determine DIB size and number of colors. The constructor allocates info header
memory but not image memory. You can also use this constructor if you need to allocate your own image
memory.
Parameter Description

size CSize object that contains the width and height of the DIB

nBitCount Bits per pixel; should be 1, 4, 8, 16, 24, or 32


Destructor—The CDib destructor frees all allocated DIB memory.
AttachMapFile—This function opens a memory-mapped file in read mode and attaches it to the CDib object. The
return is immediate because the file isn't actually read into memory until it is used. When you access the DIB,
however, a delay might occur as the file is paged in. The AttachMapFile function releases existing allocated
memory and closes any previously attached memory-mapped file.
Parameter Description

strPathname Pathname of the file to be mapped

bShare Flag that is TRUE if the file is to be opened in share mode; the default value is FALSE

Return value TRUE if successful


AttachMemory—This function associates an existing CDib object with a DIB in memory. This memory could be in
the program's resources, or it could be clipboard or OLE data object memory. Memory might have been allocated
from the CRT heap with the new operator, or it might have been allocated from the Windows heap with
GlobalAlloc.
Parameter Description

lpvMem Address of the memory to be attached

bMustDelete Flag that is TRUE if the CDib class is responsible for deleting this memory; the default value
is FALSE

hGlobal If memory was obtained with a call to the Win32 GlobalAlloc function, the CDib object needs
to keep the handle in order to free it later, assuming that bMustDelete was set to TRUE

Return value TRUE if successful


Compress—This function regenerates the DIB as a compressed or an uncompressed DIB. Internally, it converts the
existing DIB to a GDI bitmap and then makes a new compressed or an uncompressed DIB. Compression is
supported only for 4-bpp and 8-bpp DIBs. You can't compress a DIB section.
- 144 -

Parameter Description

pDC Pointer to the display device context

bCompress TRUE (default) to compress the DIB; FALSE to uncompress it

Return value TRUE if successful


CopyToMapFile—This function creates a new memory-mapped file and copies the existing CDib data to the file's
memory, releasing any previously allocated memory and closing any existing memory-mapped file. The data isn't
actually written to disk until the new file is closed, but that happens when the CDib object is reused or destroyed.
Parameter Description

strPathname Pathname of the file to be mapped

Return value TRUE if successful


CreateBitmap—This function creates a GDI bitmap from an existing DIB and is called by the Compress function. Don't
confuse this function with CreateSection, which generates a DIB and stores the handle.
Parameter Description

pDC Pointer to the display or printer device context

Return value Handle to a GDI bitmap—NULL if unsuccessful. This handle is not stored as a public
data member.
CreateSection—This function creates a DIB section by calling the Win32 CreateDIBSection function. The image
memory will be uninitialized.
Parameter Description

pDC Pointer to the display or printer device context

Return value Handle to a GDI bitmap—NULL if unsuccessful. This handle is also stored as a public data
member.
Draw—This function outputs the CDib object to the display (or to the printer) with a call to the Win32 StretchDIBits
function. The bitmap will be stretched as necessary to fit the specified rectangle.
Parameter Description

pDC Pointer to the display or printer device context that will receive the DIB image

origin CPoint object that holds the logical coordinates at which the DIB will be displayed

size CSize object that represents the display rectangle's width and height in logical units

Return value TRUE if successful


Empty—This function empties the DIB, freeing allocated memory and closing the map file if necessary.
GetDimensions—This function returns the width and height of a DIB in pixels.
Parameter Description

Return value CSize object


GetSizeHeader—This function returns the number of bytes in the info header and color table combined.
Parameter Description

Return value 32-bit integer


GetSizeImage—This function returns the number of bytes in the DIB image (excluding the info header and the color
table).
Parameter Description

Return value 32-bit integer


MakePalette—If the color table exists, this function reads it and creates a Windows palette. The HPALETTE handle is
stored in a data member.
- 145 -

Parameter Description

Return value TRUE if successful


Read—This function reads a DIB from a file into the CDib object. The file must have been successfully opened. If the
file is a BMP file, reading starts from the beginning of the file. If the file is a document, reading starts from the
current file pointer.
Parameter Description

pFile Pointer to a CFile object; the corresponding disk file contains the DIB

Return value TRUE if successful


ReadSection—This function reads the info header from a BMP file, calls CreateDIBSection to allocate image memory,
and then reads the image bits from the file into that memory. Use this function if you want to read a DIB from disk
and then edit it by calling GDI functions. You can write the DIB back to disk with Write or CopyToMapFile.
Parameter Description

pFile Pointer to a CFile object; the corresponding disk file contains the DIB

pDC Pointer to the display or printer device context

Return value TRUE if successful


Serialize—Serialization is covered in Chapter 17. The CDib::Serialize function, which overrides the MFC
CObject::Serialize function, calls the Read and Write member functions. See the Microsoft Foundation Classes
and Templates section of the online help for a description of the parameters.
SetSystemPalette—If you have a 16-bpp, 24-bpp, or 32-bpp DIB that doesn't have a color table, you can call this
function to create for your CDib object a logical palette that matches the palette returned by the
CreateHalftonePalette function. If your program is running on a 256-color palettized display and you don't call
SetSystemPalette, you'll have no palette at all, and only the 20 standard Windows colors will appear in your DIB.
Parameter Description

pDC Pointer to the display context

Return value TRUE if successful


UsePalette—This function selects the CDib object's logical palette into the device context and then realizes the
palette. The Draw member function calls UsePalette prior to painting the DIB.
Parameter Description

pDC Pointer to the display device context for realization

bBackground If this flag is FALSE (the default value) and the application is running in the foreground,
Windows realizes the palette as the foreground palette (copies as many colors as possible
into the system palette). If this flag is TRUE, Windows realizes the palette as a background
palette (maps the logical palette to the system palette as best it can).

Return value Number of entries in the logical palette mapped to the


system palette. If the function fails, the return value is GDI_ERROR.
Write—This function writes a DIB from the CDib object to a file. The file must have been successfully opened or
created.
Parameter Description

pFile Pointer to a CFile object; the DIB will be


written to the corresponding disk file.

Return value TRUE if successful


For your convenience, four public data members give you access to the DIB memory and to the DIB section handle.
These members should give you a clue about the structure of a CDib object. A CDib is just a bunch of pointers to
heap memory. That memory might be owned by the DIB or by someone else. Additional private data members
determine whether the CDib class frees the memory.
- 146 -

80.6 DIB Display Performance


Optimized DIB processing is now a major feature of Windows. Modern video cards have frame buffers that conform
to the standard DIB image format. If you have one of these cards, your programs can take advantage of the new
Windows DIB engine, which speeds up the process of drawing directly from DIBs. If you're still running in VGA mode,
however, you're out of luck; your programs will still work, but not as fast.
If you're running Windows in 256-color mode, your 8-bpp bitmaps will be drawn very quickly, either with StretchBlt or
with StretchDIBits. If, however, you are displaying 16-bpp or 24-bpp bitmaps, those drawing functions will be too
slow. Your bitmaps will appear more quickly in this situation if you create a separate 8-bbp GDI bitmap and then call
StretchBlt. Of course, you must be careful to realize the correct palette prior to creating the bitmap and prior to
drawing it.
Here's some code that you might insert just after loading your CDib object from a BMP file:
// m_hBitmap is a data member of type HBITMAP
// m_dcMem is a memory device context object of class CDC
m_pDib->UsePalette(&dc);
m_hBitmap = m_pDib->CreateBitmap(&dc); // could be slow
::SelectObject(m_dcMem.GetSafeHdc(), m_hBitmap);
Here is the code that you use in place of CDib::Draw in your view's OnDraw member function:
m_pDib->UsePalette(pDC); // could be in palette msg handler
CSize sizeDib = m_pDib->GetDimensions();
pDC->StretchBlt(0, 0, sizeDib.cx, sizeDib.cy, &m_dcMem,
0, 0, sizeToDraw.cx, sizeToDraw.cy, SRCCOPY);
Don't forget to call DeleteObject for m_hBitmap when you're done with it.
80.7 The EX11C Example
Now you'll put the CDib class to work in an application. The EX11C program displays two DIBs, one from a resource
and the other loaded from a BMP file that you select at runtime. The program manages the system palette and
displays the DIBs correctly on the printer. Compare the EX11C code with the GDI bitmap code in EX11A. Notice that
you're not dealing with a memory device context and all the GDI selection rules!
Following are the steps to build EX11C. It's a good idea to type in the view class code, but you'll want to use the
cdib.h and cdib.cpp files from the companion CD-ROM.
Run AppWizard to produce \vcpp32\ex11c\ex11c. Accept all the defaults but two: select Single Document and
select the CScrollView view base class for CEx11cView. The options and the default class names are shown
here.
- 147 -

Import the Red Blocks bitmap. Choose Resource from Visual C++'s Insert menu. Import Red Blocks.bmp from
the \WINDOWS directory. (If your version of Windows doesn't include this bitmap, load it from the companion
CD-ROM.) Visual C++ will copy this bitmap file into your project's \res subdirectory. Assign IDB_REDBLOCKS
as the ID, and save the changes.
Integrate the CDib class with this project. If you've created this project from scratch, copy the cdib.h and
cdib.cpp files from \vcpp32\ex11c on the companion CD-ROM. Simply copying the files to disk isn't enough;
you must also add the CDib files to the project. Choose Add To Project from Visual C++'s Project menu, and
then choose Files. Select cdib.h and cdib.cpp, and click the OK button. If you now switch to ClassView in the
Workspace window, you will see the class CDib and all of its member variables and functions.
Add two private CDib data members to the class CEx11cView. In the ClassView window, right-click the
CEx11cView class. Choose Add Member Variable from the resulting pop-up menu, and then add the
m_dibResource member as shown in the following illustration.

Add m_dibFile in the same way. The result should be two data members at the bottom of the header file:
CDib m_dibFile;
CDib m_dibResource;
ClassView also adds this statement at the top of the ex11cView.h file:
#include "cdib.h" // Added by ClassView
Edit the OnInitialUpdate member function in ex11cView.cpp. This function sets the mapping mode to
MM_HIMETRIC and loads the m_dibResource object directly from the IDB_REDBLOCKS resource. Note that
- 148 -

we're not calling LoadBitmap to load a GDI bitmap as we did in EX11A. The CDib::AttachMemory function
connects the object to the resource in your EXE file. Add the following boldface code:
void CEx11cView::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
CSize sizeTotal(30000, 40000); // 30-by-40 cm
CSize sizeLine = CSize(sizeTotal.cx / 100, sizeTotal.cy / 100);
SetScrollSizes(MM_HIMETRIC, sizeTotal, sizeTotal, sizeLine);

LPVOID lpvResource = (LPVOID) ::LoadResource(NULL,


::FindResource(NULL, MAKEINTRESOURCE(IDB_REDBLOCKS),
RT_BITMAP));
m_dibResource.AttachMemory(lpvResource); // no need for
// ::LockResource
CClientDC dc(this);
TRACE("bits per pixel = %d\n", dc.GetDeviceCaps(BITSPIXEL));
}
Edit the OnDraw member function in the file ex11cView.cpp. This code calls CDib::Draw for each of the DIBs.
The UsePalette calls should really be made by message handlers for the WM_QUERYNEWPALETTE and
WM_PALETTECHANGED messages. These messages are hard to deal with because they don't go to the view
directly, so we'll take a shortcut. Add the following boldface code:
void CEx11cView::OnDraw(CDC* pDC)
{
BeginWaitCursor();
m_dibResource.UsePalette(pDC); // should be in palette
m_dibFile.UsePalette(pDC); // message handlers, not here
pDC->TextOut(0, 0,
"Press the left mouse button here to load a file.");
CSize sizeResourceDib = m_dibResource.GetDimensions();
sizeResourceDib.cx *= 30;
sizeResourceDib.cy *= -30;
m_dibResource.Draw(pDC, CPoint(0, -800), sizeResourceDib);
CSize sizeFileDib = m_dibFile.GetDimensions();
sizeFileDib.cx *= 30;
sizeFileDib.cy *= -30;
m_dibFile.Draw(pDC, CPoint(1800, -800), sizeFileDib);
EndWaitCursor();
}
Map the WM_LBUTTONDOWN message in the CEx11cView class. Edit the file ex11cView.cpp.
OnLButtonDown contains code to read a DIB in two different ways. If you leave the
MEMORY_MAPPED_FILES definition intact, the AttachMapFile code is activated to read a memory-mapped
file. If you comment out the first line, the Read call is activated. The SetSystemPalette call is there for DIBs that
don't have a color table. Add the following boldface code:
#define MEMORY_MAPPED_FILES
void CEx11cView::OnLButtonDown(UINT nFlags, CPoint point)
{
CFileDialog dlg(TRUE, "bmp", "*.bmp");
if (dlg.DoModal() != IDOK) {
return;
}
#ifdef MEMORY_MAPPED_FILES
if (m_dibFile.AttachMapFile(dlg.GetPathName(),
TRUE) == TRUE) { // share
Invalidate();
}
#else
- 149 -

CFile file;
file.Open(dlg.GetPathName(), CFile::modeRead);
if (m_dibFile.Read(&file) == TRUE) {
Invalidate();
}
#endif // MEMORY_MAPPED_FILES
CClientDC dc(this);
m_dibFile.SetSystemPalette(&dc);
}
Build and run the application. The bitmaps directory on the companion CD-ROM contains several interesting
bitmaps. The Chicago.bmp file is an 8-bpp DIB with 256-color table entries; the forest.bmp and clouds.bmp files
are also 8-bpp, but they have smaller color tables. The balloons.bmp is a 24-bpp DIB with no color table. Try
some other BMP files if you have them. Note that Red Blocks is a 16-color DIB that uses standard colors, which
are always included in the system palette.
81. Going Further with DIBs
Each new version of Windows offers more DIB programming choices. Both Windows 95 and Microsoft Windows NT
4.0 provide the LoadImage and DrawDibDraw functions, which are useful alternatives to the DIB functions already
described. Experiment with these functions to see if they work well in your applications.
81.1 The LoadImage Function
The LoadImage function can read a bitmap directly from a disk file, returning a DIB section handle. It can even
process OS/2 format DIBs. Suppose you wanted to add an ImageLoad member function to CDib that would work like
ReadSection. This is the code you would add to cdib.cpp:
BOOL CDib::ImageLoad(const char* lpszPathName, CDC* pDC)
{
Empty();
m_hBitmap = (HBITMAP) ::LoadImage(NULL, lpszPathName,
IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
DIBSECTION ds;
VERIFY(::GetObject(m_hBitmap, sizeof(ds), &ds) == sizeof(ds));
// Allocate memory for BITMAPINFOHEADER
// and biggest possible color table
m_lpBMIH = (LPBITMAPINFOHEADER) new
char[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
memcpy(m_lpBMIH, &ds.dsBmih, sizeof(BITMAPINFOHEADER));
TRACE("CDib::LoadImage, biClrUsed = %d, biClrImportant = %d\n",
m_lpBMIH->biClrUsed, m_lpBMIH->biClrImportant);
ComputeMetrics(); // sets m_lpvColorTable
m_nBmihAlloc = crtAlloc;
m_lpImage = (LPBYTE) ds.dsBm.bmBits;
m_nImageAlloc = noAlloc;
// Retrieve the DIB section's color table
// and make a palette from it
CDC memdc;
memdc.CreateCompatibleDC(pDC);
::SelectObject(memdc.GetSafeHdc(), m_hBitmap);
UINT nColors = ::GetDIBColorTable(memdc.GetSafeHdc(), 0, 256,
(RGBQUAD*) m_lpvColorTable);
if (nColors != 0) {
ComputePaletteSize(m_lpBMIH->biBitCount);
MakePalette();
}
// memdc deleted and bitmap deselected
return TRUE;
}
- 150 -

Note that this function extracts and copies the BITMAPINFOHEADER structure and sets the values of the CDib
pointer data members. You must do some work to extract the palette from the DIB section, but the Win32
GetDIBColorTable function gets you started. It's interesting that GetDIBColorTable can't tell you how many palette
entries a particular DIB uses. If the DIB uses only 60 entries, for example, GetDIBColorTable generates a 256-entry
color table with the last 196 entries set to 0.
81.2 The DrawDibDraw Function
Windows includes the Video for Windows (VFW) component, which is supported by Visual C++. The VFW
DrawDibDraw function is an alternative to StretchDIBits. One advantage of DrawDibDraw is its ability to use dithered
colors. Another is its increased speed in drawing a DIB with a bpp value that does not match the current video mode.
The main disadvantage is the need to link the VFW code into your process at runtime.
Shown below is a DrawDib member function for the CDib class that calls DrawDibDraw:
BOOL CDib::DrawDib(CDC* pDC, CPoint origin, CSize size)
{
if (m_lpBMIH == NULL) return FALSE;
if (m_hPalette != NULL) {
::SelectPalette(pDC->GetSafeHdc(), m_hPalette, TRUE);
}
HDRAWDIB hdd = ::DrawDibOpen();
CRect rect(origin, size);
pDC->LPtoDP(rect); // Convert DIB's rectangle
// to MM_TEXT coordinates
rect -= pDC->GetViewportOrg();
int nMapModeOld = pDC->SetMapMode(MM_TEXT);
::DrawDibDraw(hdd, pDC->GetSafeHdc(), rect.left, rect.top,
rect.Width(), rect.Height(), m_lpBMIH, m_lpImage, 0, 0,
m_lpBMIH->biWidth, m_lpBMIH->biHeight, 0);
pDC->SetMapMode(nMapModeOld);
VERIFY(::DrawDibClose(hdd));
return TRUE;
}
Note that DrawDibDraw needs MM_TEXT coordinates and the MM_TEXT mapping mode. Thus, logical coordinates
must be converted not to device coordinates but to pixels with the origin at the top left of the scrolling window.
To use DrawDibDraw, your program needs an #include<vfw.h> statement, and you must add vfw32.lib to the list of
linker input files. DrawDibDraw might assume the bitmap it draws is in read/write memory, a fact to keep in mind if
you map the memory to the BMP file.
82. Putting Bitmaps on Pushbuttons
The MFC library makes it easy to display a bitmap (instead of text) on a pushbutton. If you were to program this from
scratch, you would set the Owner Draw property for your button and then write a message handler in your dialog
class that would paint a bitmap on the button control's window. If you use the MFC CBitmapButton class instead, you
end up doing a lot less work, but you have to follow a kind of "cookbook" procedure. Don't worry too much about how
it all works (but be glad that you don't have to write much code!).

There's also another way to put bitmaps on buttons. See Chapter 36, for a description of the
CButton::SetBitmap function, which associates a single bitmap with a button.
To make a long story short, you lay out your dialog resource as usual with unique text captions for the buttons you
designate for bitmaps. Next you add some bitmap resources to your project, and you identify those resources by
name rather than by numeric ID. Finally you add some CBitmapButton data members to your dialog class, and you
call the AutoLoad member function for each one, which matches a bitmap name to a button caption. If the button
caption is "Copy", you add two bitmaps: "COPYU" for the up state and "COPYD" for the down state. By the way, you
must still set the button's Owner Draw property. (This will all make more sense when you write a program).

If you look at the MFC source code for the CBitmapButton class, you'll see that the bitmap is an
ordinary GDI bitmap painted with a BitBlt call. Thus, you can't expect any palette support. That's not
often a problem because bitmaps for buttons are usually 16-color bitmaps that depend on standard
VGA colors.
82.1 The EX11D Example
Here are the steps for building EX11D:
- 151 -

Run AppWizard to produce \vcpp32\ex11d\ex11d. Accept all the defaults but three: select Single Document,
deselect Printing And Print Preview, and select Context-Sensitive Help. The options and the default class
names are shown in the illustration below.
The Context-Sensitive Help option was selected for one reason only: it causes AppWizard to copy some bitmap
files into your project's \hlp subdirectory. These bitmaps are supposed to be bound into your project's help file,
but we won't study help files until Chapter 21.

Modify the project's IDD_ABOUTBOX dialog resource. It's too much hassle to create a new dialog resource for
a few buttons, so we'll use the About dialog that AppWizard generates for every project. Add three pushbuttons
with captions, as shown below, accepting the default IDs IDC_BUTTON1, IDC_BUTTON2, and
IDC_BUTTON3. The size of the buttons isn't important because the framework adjusts the button size at
runtime to match the bitmap size.

Select the Owner Draw property for all three buttons.


Import three bitmaps from the project's \hlp subdirectory. Choose Resource from Visual C++'s Insert menu, and
then click the Import button. Start with EditCopy.bmp, as shown below.
- 152 -

Assign the name "COPYU" as shown.

Be sure to use quotes around the name in order to identify the resource by name rather than by ID. This is now
the bitmap for the button's up state. Close the bitmap window and, from the ResourceView window, use the
clipboard (or drag and drop) to make a copy of the bitmap. Rename the copy "COPYD" (down state), and then
edit this bitmap. Choose Invert Colors from the Image menu. There are other ways of making a variation of the
up image, but inversion is the quickest.
Repeat the steps listed above for the EditCut and EditPast bitmaps. When you're finished, you should have the
following bitmap resources in your project.
Resource Name Original File Invert Colors

"COPYU" EditCopy.bmp no

"COPYD" EditCopy.bmp yes

"CUTU" EditCut.bmp no

"CUTD" EditCut.bmp yes

"PASTEU" EditPast.bmp no

"PASTED" EditPast.bmp yes


Edit the code for the CAboutDlg class. Both the declaration and the implementation for this class are contained
in the ex11d.cpp file. First add the three private data members shown here in the class declaration:
CBitmapButton m_editCopy;
CBitmapButton m_editCut;
CBitmapButton m_editPaste;
Then you use ClassWizard to map the WM_INITDIALOG message in the dialog class. (Be sure that the
CAboutDlg class is selected.) The message handler (actually a virtual function) is coded as follows:
BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
VERIFY(m_editCopy.AutoLoad(IDC_BUTTON1, this));
VERIFY(m_editCut.AutoLoad(IDC_BUTTON2, this));
VERIFY(m_editPaste.AutoLoad(IDC_BUTTON3, this));
return TRUE; // return TRUE unless you set the focus to a control
- 153 -

// EXCEPTION: OCX Property Pages should return FALSE


}
The AutoLoad function connects each button with the two matching resources. The VERIFY macro is an MFC
diagnostic aid that displays a message box if you didn't code the bitmap names correctly.
Edit the OnDraw function in ex11dView.cpp. Replace the AppWizard-generated code with the following line:
pDC->TextOut(0, 0, "Choose About from the Help menu.");
Build and test the application. When the program starts, choose About from the Help menu and observe the
button behavior. The image below shows the CUT button in the down state.

Note that bitmap buttons send BN_CLICKED notification messages just as ordinary buttons do. ClassWizard
can, of course, map those messages in your dialog class.
82.2 Going Further with Bitmap Buttons
You've seen bitmaps for the buttons' up and down states. The CBitmapButton class also supports bitmaps for the
focused and disabled states. For the Copy button, the focused bitmap name would be "COPYF", and the disabled
bitmap name would be "COPYX". If you want to test the disabled option, make a "COPYX" bitmap, possibly with a
red line through it, and then add the following line to your program:
m_editCopy.EnableWindow(FALSE);
Chapter Twelve
83. Windows Message Processing and Multithreaded Programming
With its multitasking and multithreading API, Win32 revolutionized programming for Microsoft Windows. If you've
seen magazine articles and advanced programming books on these subjects, you might have been intimidated by
the complexity of using multiple threads. You could stick with single-threaded programming for a long time and still
write useful Win32 applications. If you learn the fundamentals of threads, however, you'll be able to write more
efficient and capable programs. You'll also be on your way to a better understanding of the Win32 programming
model.
84. Windows Message Processing
To understand threads, you must first understand how 32-bit Windows processes messages. The best starting point
is a single-threaded program that shows the importance of the message translation and dispatch process. You'll
improve that program by adding a second thread, which you'll control with a global variable and a simple message.
Then you'll experiment with events and critical sections. For heavy-duty multithreading elements such as mutexes
and semaphores, however, you'll need to refer to another book, such as Jeffrey Richter's Advanced Windows, 3d Ed.
(Microsoft Press, 1997).
84.1 How a Single-Threaded Program Processes Messages
All the programs so far in this book have been single-threaded, which means that your code has only one path of
execution. With ClassWizard's help, you've written handler functions for various Windows messages and you've
written OnDraw code that is called in response to the WM_PAINT message. It might seem as though Windows
magically calls your handler when the message floats in, but it doesn't work that way. Deep inside the MFC code
(which is linked to your program) are instructions that look something like this:
MSG message;
while (::GetMessage(&message, NULL, 0, 0)) {
::TranslateMessage(&message);
::DispatchMessage(&message);
}
Windows determines which messages belong to your program, and the GetMessage function returns when a
message needs to be processed. If no messages are posted, your program is suspended and other programs can
run. When a message eventually arrives, your program "wakes up." The TranslateMessage function translates
WM_KEYDOWN messages into WM_CHAR messages containing ASCII characters, and the DispatchMessage
function passes control (via the window class) to the MFC message pump, which calls your function via the message
map. When your handler is finished, it returns to the MFC code, which eventually causes DispatchMessage to return.
84.2 Yielding Control
- 154 -

What would happen if one of your handler functions was a pig and chewed up 10 seconds of CPU time? Back in the
16-bit days, that would have hung up the whole computer for the duration. Only cursor tracking and a few other
interrupt-based tasks would have run. With Win32, multitasking got a whole lot better. Other applications can run
because of preemptive multitasking—Windows simply interrupts your pig function when it needs to. However, even in
Win32, your program would be locked out for 10 seconds. It couldn't process any messages because
DispatchMessage doesn't return until the pig returns.
There is a way around this problem, however, which works with both Win16 and Win32. You simply train your pig
function to be polite and yield control once in a while by inserting the following instructions inside the pig's main loop:
MSG message;
if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
::TranslateMessage(&message);
::DispatchMessage(&message);
}
The PeekMessage function works like GetMessage, except that it returns immediately even if no message has
arrived for your program. In that case, the pig keeps on chewing. If there is a message, however, the pig pauses, the
handler is called, and the pig starts up again after the handler exits.
84.3 Timers
A Windows timer is a useful programming element that sometimes makes multithreaded programming unnecessary.
If you need to read a communication buffer, for example, you can set up a timer to retrieve the accumulated
characters every 100 milliseconds. You can also use a timer to control animation because the timer is independent of
CPU clock speed.
Timers are easy to use. You simply call the CWnd member function SetTimer with an interval parameter, and then
you provide, with the help of ClassWizard, a message handler function for the resulting WM_TIMER messages.
Once you start the timer with a specified interval in milliseconds, WM_TIMER messages will be sent continuously to
your window until you call CWnd::KillTimer or until the timer's window is destroyed. If you want to, you can use
multiple timers, each identified by an integer. Because Windows isn't a real-time operating system, the interval
between timer events becomes imprecise if you specify an interval much less than 100 milliseconds.
Like any other Windows messages, timer messages can be blocked by other handler functions in your program.
Fortunately, timer messages don't stack up. Windows won't put a timer message in the queue if a message for that
timer is already present.
84.4 The EX12A Program
We're going to write a single-threaded program that contains a CPU-intensive computation loop. We want to let the
program process messages after the user starts the computation; otherwise, the user couldn't cancel the job. Also,
we'd like to display the percent-complete status by using a progress indicator control, as shown in Figure 12-1. The
EX12A program allows message processing by yielding control in the compute loop. A timer handler updates the
progress control based on compute parameters. The WM_TIMER messages could not be processed if the compute
process didn't yield control.

Figure 12-1. The Compute dialog box.


Here are the steps for building the EX12A application:
Run AppWizard to generate \vcpp32\ex12a\ex12a. Accept all the default settings but two: select Single
Document and deselect Printing And Print Preview. The options and th