QuickC For Windows
QuickC For Windows
\ > • J* \
6
QuickC for Windows
The Peter Norton Programming Series
What's Inside
• An abundance of practical ready-to-run programs that show the best ways
to handle the keyboard, files and disks, graphics and the screen, the mouse,
and more
• Expert tips on writing QuickC for Windows programs, for more speed and
power programming
• A learn-by-doing approach to programming that shows code in action in
a direct, highly readable style
The Peter Norton Hardware Library gives you an insider's grasp of your
computer and the way it works. Included are such best-selling classics as
Inside the IBM PC, Inside the Apple Macintosh, and The Hard Disk Companion.
The Peter Norton Programming Library focuses on creating programs that
work right away and offers the best tips and techniques in the industry. It
includes Advanced BASIC, C Programming, C++ Programming, QBasic Pro¬
gramming, Advanced DOS, and more.
Digitized by the Internet Archive
in 2018 with funding from
Kahle/Austin Foundation
https://archive.org/details/quickcforwindowsOOOOholz
QuickC for
Windows
Steve Holzner
The Peter Norton Computing Group
Brady
New York London Toronto Sydney Singapore Tokyo
Copyright © 1992 by Peter Norton
All rights reserved,
including the right of reproduction
in whole or in part in any form.
Brady
Holzner, Steven.
Quick C for Windows / by Steven Holzner and the Peter Norton
Computing Group,
p. cm.
Includes index.
1. C (Computer programming language) 2. Microsoft QuickC (Computer
program) 3. Windows (Computer programs) I. Peter Norton Computing
Group II. Title.
QA76.73.C15H6593 1992
005.4’3-dc20 91-42522
CIP
ISBN 0-13-747569-1
Contents
Introduction ix
CHAPTER 1
CHAPTER 2
Hungarian Notation 38
The WinMain( ) Program 39
The Message Loop 60
The Window Procedure 68
QuickC for Windows Projects 73
v
vi Peter Norton’s QuickC for Windows
CHAPTER 3
CHAPTER 4
Menus m
Menu Conventions 117
Adding Menus to Our Programs 118
ALT Keys and Menu Accelerators 135
Graying Out and Checking Menu Items 137
Adding Menu Items at Run-Time 148
CHAPTER 5
CHAPTER 6
CHAPTER 7
CHAPTER 8
CHAPTER 9
CHAPTER 10
Index 441
Limits of Liability and Disclaimer of Warranty
The authors and publisher of this book have used their best efforts in prepar¬
ing this book and the programs contained in it. These efforts include the
development, research, and testing of the theories and programs to deter¬
mine their effectiveness. The authors and publisher make no warranty of any
kind, expressed or implied, with regard to these programs or the documenta¬
tion contained in this book. The authors and publisher shall not be liable in
any event for incidental or consequential damages in connection with, or
arising out of, the furnishing, performance, or use of these programs.
Trademarks
QuickC for Windows helps to change this picture dramatically by adding some
of the programming tools that have been lacking for so long in Windows.
IX
x Peter Norton's QuickC for Windows
In particular, we’ll see that QuickC for Windows provides us with a number of
programming tools and that much of the painful programming that Windows
applications require will be handled for us automatically. This is indeed a
blessing; almost all of the user interface part of our programs (window size,
location, menus, dialog boxes, and so on) can be designed quickly and easily
under QuickC for Windows. It is only when customizing our application —
that is, when adding the noninterface part of the program (the “guts” of the
program: data handling, file opening, and so on) — that we’ll have to start
writing our own code.
Our Approach
This is a book for would-be Windows programmers, so we’re going to spend a
lot of time seeing QuickC for Windows at work. In other words, we’re going to
see what the software is capable of — and to do that, we’ll have to see plenty
of examples, which is always the best way to learn about software. We’ll start
almost at once in Chapter 1, getting a window on the screen and working with
it. As the book progresses, we’ll see many other examples, such as a database
program, a pop-up notepad, a file scanner program, and others.
If you’re unfamiliar with Windows programming, you’ll also find that the code
we develop is different from what you might expect; in particular, Windows
programs are event driven, which means that our code will be divided up into
many small sections to handle specific events, rather than the linear, continu¬
ous programs that you might be used to. This can take some getting used to,
but after a while, thinking in terms of Windows events like mouse clicks or key
presses comes naturally. We’ll see more about this in Chapter 1.
NOTE It’s important to realize that although this book is about programming in
Windows, it’s not a complete Windows reference. Although we do cover a
great deal of Windows programming, our subject here is QuickC for Win¬
dows— we don’t have the space to deal with all of the 2,000+ Windows
functions. In fact, it would take a book many times the size of this one to
make up a complete Windows reference. If your goal is to become a profes¬
sional Windows programmer, you’ll have to read a few of the thousand-
page (and up) Windows books now available in addition to this one. (And
even those books are not complete Windows references!)
For that reason, part of the first chapter will get us started by exploring the
concepts we’ll need. We’ll begin with fundamental Windows concepts — like
xii Peter Norton’s QuickC for Windows
windows and buttons — and then we’ll work through some Windows pro¬
gramming concepts. Finally, we’ll get an introduction to the essential QuickC
for Windows programming concepts. This will form the foundation of all we
do in the book, so we’ll make sure that we get all the basic ideas down before
continuing.
From then on, our coverage will be task oriented as much as possible. Most of
the successive chapters are purposely designed to cover one specific part of
Windows, such as dialog boxes, mouse handling, or graphics. In other words,
we’ll cover Windows the way the programmer wants to see it — in terms of
what we can do with it (pop a dialog box on the screen, construct a menu, use
the mouse), not in terms of abstract programming principles. This way, we’ll
build our expertise by building our windows — piece by piece, steadily adding
more and more power to our Windows applications. And this will allow us to
handle the complexities that might arise in a systematic, gradual way.
After we build and design our applications, we’ll turn more and more behind
the scenes, towards adding power to the programming part of our applica¬
tions (as opposed to the I/O part, which is handled by menus and buttons).
We’ll investigate how to work with files, as well as advanced programming
concepts and fast, efficient data handling.
All this makes for quite an ambitious plan, then — learning how to design and
put to work serious Windows applications with a minimum of trouble. Nor¬
mally, this would mean a book full of huge programs, each of which we would
have to write from scratch. However, we’ll see that QuickC for Windows is a
whole different story. Getting our Windows programs working — and produc¬
ing real results — will often be a matter of simply designing what we want on
the screen and then letting QCWin handle the details.
In addition, you’ll need Windows, version 3.0 or later, because QuickC for
Windows requires it. There’s nothing special here, just the normal Win.Exe
program — but please note that you should be a Windows user before becom¬
ing a Windows programmer. That is, if you don’t know how to use Windows and
aren’t used to the customary feel of Windows applications, you should take
some time to learn before working on programs. Windows users expect their
applications to conform to Windows’ conventions pretty closely (e.g., making
sure that the last item in a File menu is “Exit,” or allowing a user to select an
item in a list box either by highlighting it and clicking an OK button, or by
double clicking the item itself), and the best way to know what’s expected is by
being a Windows user yourself.
NOTE We should also note that you’ll need a mouse (or other pointing device) for
the work we’ll do in this book. While Windows applications are supposed to
run with either the mouse or the keyboard, it’s very difficult to do real work
without a mouse — and it’s certainly almost impossible to program in
QuickC for Windows without one. That is, casual Windows users may be
able to get along without a mouse, but for the more serious QuickC for
Windows programmer, the mouse is an essential tool.
Finally, of course, you’ll need QuickC for Windows itself; any version will do.
If you haven’t installed it yet, just run the Setup program under Windows as
explained in the QuickC for Windows documention. Besides Windows,
QuickC for Windows itself is all the software we need — we’ll be able to enter
programs directly without the assistance of a word processor or text editor.
That’s it; we’re ready to begin. Our first first task will be to get a simple window
on the screen as soon as possible — so let’s dig in immediately with Chapter 1.
v
'
'
Welcome to QuickC for
Windows
Welcome to QuickC for Windows, one of the most exciting software packages
now in the PC marketplace, and one of the components of a revolution in
Windows programming. This powerful package is one of the new generation
of programming tools that are beginning to open up Windows programming
as never before. No longer will it take a great deal of patience, experience, and
expensive software to produce valuable Windows applications — under
QuickC for Windows (and programs like it), developing Windows programs
will become easier than ever.
In this chapter, we’ll put together our first QuickC for Windows programs;
these programs will run under Windows 3.x and above. And we’ll see that
QuickC for Windows handles many of the details for us. You can think of
QuickC for Windows as an immense box of tools and resources, waiting for us
to use. To use these tools and resources, however, we’ll have to understand
them. In other words, we’ll have to know what’s available — and all about the
environment in which it’s available — before we can take advantage of all that
QuickC for Windows has to offer us.
Accordingly, we’ll begin our tour of QuickC for Windows by examining the
environment in which we will work — Windows itself. Next, we’ll see how
QuickC for Windows works in this environment and what tools it offers us to
manipulate that environment. Then, when we’re ready, we’ll put QuickC for
1
2 Peter Norton’s QuickC for Windows
Windows to work and get some results. Let’s begin now by examining our host
operating environment, Windows itself.
About Windows
Many people believe that Graphical User Interfaces — GUIs — are the wave
of the future in microcomputing, and they might be right. Certainly Windows
3.0 has been the quickest selling software package in history (500,000 copies
in its first six weeks; 3,000,000 in its first nine months). In most significant
ways, Windows is a full operating environment by itself.
Windows is also very different from DOS in several ways; one of the most
fundamental is that Windows is a Graphical User Interface, which introduces
many new concepts. One of the primary ideas here is that most of the available
options in a program are presented to the user at once in the form of objects
on the screen, much like tools, ready to be used. The utility of this simple
approach is surprising — rather than remembering complex techniques and
keywords, a user can simply select the correct tool for the task at hand and
begin work. In this way, graphical interfaces fufill much of the promise of
computers as endlessly adaptable tools. Let’s take a look at some of the back¬
ground of this operating environment first, and then we’ll dig into the details
afterwards.
Microsoft actually started working on Windows in 1983, only two years after
the PC had appeared. However, the original version, Windows 1.01, didn’t
actually ship until 1985. This version was supposed to run on the standard
machine of that time: an IBM PC with two 360K diskette drives, 256K, and an
8088. The display was automatically tiled; that is, the windows were arranged
to cover the whole screen, and it looked less than impressive.
The next major version, Windows 2, came out two years later. For the first
time, windows could overlap on the screen. However, Windows 2 could only
run in 80x86 real mode, which meant that it was limited to a total of one
megabyte of memory. For a while, Windows even split into Windows 286 and
Windows 386 to take advantage of the capabilities of the (then new) 80386
chip. Progress had been made, but it was clear that much more was still
needed.
Welcome to QuickC for Windows 3
Finally, in May of 1990, Microsoft introduced Windows 3.0. The look and feel
of Windows 3.0 was a great improvement over its ancestors, and it featured
proportional fonts, which made displays look more refined (although, as we’ll
see, they can make life more difficult for the programmer). Version 3.0 also
has better support for DOS programs, and many users are starting to use
Windows as the primary operating system for their PCs.
Before starting to program, then, let’s spend a little time reviewing Windows
terminology ourselves; this will help us later in the book. At the upper left of
the window in Figure 1-1 is a system menu box, which, when selected, displays
a menu that typically allows the user to move the window, close it, or minimize
it. At the top center is the title or caption bar, and this provides an easy way of
labelling an application.
To the right of the title bar are the Minimize and Maximize boxes, which allow
the user to either reduce the window to an icon (called an application’s iconic
4 Peter Norton’s QuickC for Windows
Maximize Box
Menu Bar
Vertical Scroll
Bar
Client Area
state) or expand it fully, usually to the whole screen. Under the caption bar is
usually a menu bar indicating the currently available menu options for the
application. In almost every stand-alone application, there will be a menu bar
with at least one menu item in it: the File menu. This is the menu that usually
offers the Exit item at the bottom, as shown in Figure 1-2.
NOTE The Exit item is the usual way for users to leave an application, so if your
application supports file handling, you should include the Exit item at the
bottom of your File menu.
Under the menu bar is the client area’, in fact, the client area makes up the
whole of a window under the menu bar except for the borders and scroll bars
Save
Save As...
Print
Page Setup...
Printer Setup
Exit
(it’s the area that the window is designed to display). This is our drawing area,
the area we will work with directly in QuickC for Windows; that is, this is the
part of the window on which we’ll place graphics and text.
To the right of the client area is a vertical scroll bar, which is a common part
of a window that displays text (or, for that matter, other graphics). If there is
too much text to fit in the window at once, scroll bars let you look at some
subsection of the whole, moving around in the document. The small square
that moves up and down and which you use to manipulate your position in the
scroll bar is called the thumb.
On the bottom of the window is another scroll bar, a horizontal scroll bar,
which scrolls the information in the client area horizontally. Everything in the
window but the client area is called the nonclient area; even the border is part
of the nonclient area. Windows will be responsible for maintaining the non¬
client area of the window, and we’ll be responsible for the client area.
There are many aspects of the way users expect Windows applications to work
that you should be familiar with before producing applications youself; in
other words, there is a large number of Windows conventions that you should
adhere to. Although we’ll discuss these conventions as we reach the appropri¬
ate topics, there’s no substitute for working with existing Windows applica¬
tions to get the correct feel for the Windows interface.
After a while, these conventions become quite automatic; for instance, in file
list boxes (where the program is showing you which files are available to be
opened), one click of the mouse should highlight a filename (called selecting),
and two clicks should open the file (called choosing). On the other hand, it is
also supposed to be possible to use Windows without a mouse at all —just with
the keyboard — so you must provide keyboard support at the same time (in
this case, the user would use the tab key to move to the correct box, the arrow
keys to highlight a filename, and the Enter key to choose it).
6 Peter Norton’s QuickC for Windows
NOTE For the purposes of program design in this book, we are assuming that you
have a mouse to go along with QuickC for Windows. Although it is possible
to use Windows applications without a mouse, Windows programmers (or even
experienced Windows users) are severely hampered without one, seriously
crippling their productivity.
There are other conventions that Windows users expect; if there’s some object
that can be moved around the screen, users expect to be able to drag it with
the mouse. They expect accelerator keys in menus, system menus that let
them close a window, and windows that can be moved, resized, or minimized.
In general, the best way to know what will be expected of your program is to
work with existing Windows applications.
iinclude <stdio.h>
main()
{
printf("Hello, world.");
return(0);
}
In the first line, we’re simply printing out a message: “Hello, world.” After that
line, control continues sequentially to the return (0) line; at that point, the
program finishes up and returns to DOS. If there were more statements in this
program, control would usually progress through them, looping and progress¬
ing in the way that the programmer designed it to work. However, Windows is
different.
Windows Events
An application under Windows typically presents all possible options (in the
form of visual objects) on the screen for the user to select for themselves. In
this way, it represents a new kind of programming — event driven program-
Welcome to QuickC for Windows 7
Instead, we’ll have to write separate code for each button. That’s going to be
the case in general, and it will have significant consequences for us in this
book. Instead of monolithic programs that you can read from beginning to
end, our code will necessarily be divided up into sections, one such section for
one kind of event.
That’s how event driven programming works: we’ll largely be designing our
code around the interface; that is, around the way we’ve set up the window, at
least in the early part of this book. Our programs won’t have “modes,” like a
editor can have modes (e.g., insert mode, marking mode, and so on); instead,
all the options available at one time will be represented on the screen, ready
to be used. Let’s look into the process of writing such programs next, as well
as taking a look at the programming tools we’ll use in this book.
you’ve never created actual Windows applications before, then you’ll find that
this kind of programming is like nothing you’ve ever seen before.
So just what is QuickC for Windows? It’s a programming package that contains
a number of powerful parts: an integrated development environment where
we’ll edit our code and run our programs (QCWIN itself); a tool that will let
us design dialog boxes (the Dialog Editor); another tool that will actually write
Windows software for us (called QuickCase:W); and one last tool that will let
us design our own Windows icons (the Image Editor). We’ll see all of them in
this book. This package of tools together with the development environment
will let us bring C up to speed in Windows, allowing us to coordinate all the
steps of creating functioning Windows programs as easily as possible.
There are three major steps to writing an application in QuickC for Windows,
and we’ll follow them throughout this book. Here they are:
The first step — designing the program’s visual interface — is where the tools
included with QuickC for Windows can really help. Before, it was a tedious
process to design the appearance of windows, where the buttons would go,
how large they would be and all types of other considerations. Adding or
removing features was also very difficult. Under QuickC for Windows, how¬
ever, this whole process has become easy. We’ll see that often we’ll be able to
simply draw —just like a paint program — the window(s) we want, as well as
all the buttons, boxes, and labels we want. In other words, we’ll see the actual
appearance of our application at design time. Adding or removing buttons or
boxes can work just as it would in a paint program, as we’ll see; there’s no
difficult programming involved at all.
The next step involves CASE, Computer Aided Software Design. One of the
tools that comes with the QCWin package is QuickCase:W (and the Case part
of that is indeed Computer Aided Software Design). We’ll see that most of the
interface part of the program — menus, dialog boxes, and so on, can be
handled right here. We just have to design the window the way we want it, add
the dialog boxes and buttons we want, and let QuickCase:W generate the C
code.
Welcome to QuickC for Windows 9
From there, we really start with QuickC for Windows. The user interface has
been done at this stage — that is, when the user selects a certain menu choice,
the program will let us know which one was selected. Now it’s up to us to add
all the rest of the code. For example, if the menu selection was Open File,
we’d have to add the code that opened the indicated file at this point. That is,
all the noninterface code — the code that makes our program unique — is
our responsibility. And to add that code, we’ll have to be familiar not only with
C, but with many of the Windows functions available to us. For example, since
Windows is a multitasking operating system, we can’tjustuse fopen( ) to open
a file; instead, we should use Window’s OpenFile( ) function.
That’s how the program design process works in outline; now let’s see it in
practice.
#include <stdio.h>
main()
{
printf("Hello, world.");
return(0) ;
}
Under DOS, you would type it into a file called, maybe, hello.c, compile it, link
it, and run it. When you do, the words “Hello, world.” would appear on the
screen.
Now let’s investigate Windows instead of DOS. It turns out that there’s an
intermediate way of running a program like this under Windows without
converting it fully into a Windows program. QuickC for Windows allows you to
run pre-existing C programs in a window just as they might run under DOS.
This can often make the conversion process much easier, but note that we
won’t have available to us any of the aspects that make Windows applications
so popular — windows, buttons, scroll bars, and so on. In other words, it will
just be as if our DOS screen has been placed into a window instead (and you
can use all the normal DOS C functions, including graphics functions as
under DOS). To do this, start QCWin up by double clicking its icon in Win-
10 Peter Norton’s QuickC for Windows
The development enviroment will be the brain center for developing our
Windows projects. All the code and visual images that we generate with the
rest of the QCWin package will come together here. This is where we’ll be able
to edit the code that those tools produce for us, adding the rest of the code
that makes our program unique. That is, QCWin’s development environment
is just like QuickC’s development environment under DOS — you can edit,
compile, test, and debug C programs here. The major differences are that
QCWin’s development environment is specially made to produce Windows
applications, so it automatically compiles and links not just C files, but all the
other specialized types of files that we’ll need for our Windows application. In
addition, of course, the development environment runs under Windows, so it
appears in a window itself.
NOTE We’ll see in a minute that the QCWin development environment can also
produce DOS .exe files as well as Windows .exe files.
Let’s type in the above program now. To do that, simply open the QCWin File
menu with the mouse, as shown in Figure 1-5. This File menu functions much
as does any file menu in a Windows application. With it, we can open files, save
them, and print them out. At the bottom of the file menu, QCWin will display
the last four files you’ve been working on for easy reference. To create our
Welcome to QuickC for Windows 11
program, select New in the File menu. A window appears in the development
environment labelled <1> UNTITLED 1. This is the default name for files; the
<1> means that this is window 1 in the development environment, which will
make switching between them easier.
You might notice that the actual name of the file in Figure 1-5 is UNTI¬
TLED 1*. The asterisk (*) after a file’s name in QCWin means that a file has
been changed but not yet saved.
Now all we have to do is to enter the hello.c program, as shown in Figure 1-6,
and run it. After entering it, use the Save As... item in the File menu to give it
a name — say hello.c. We can run it by first selecting the Project... item in the
Options menu. When we do, the Project dialog box opens, as shown in Figure
1-7. This dialog box indicates the different types of .exe files that we can
create: Windows .exe files, QuickWin .exe files — even DOS .exe files (i.e., just
like the ones DOS QuickC would produce). In addition, we can produce
Windows run-time libraries, which have the extension .dll (for dynamic link
libraries). The QuickWin option is the one we want here; with this option, you
can run pre-existing DOS C programs in their own window, without any spe¬
cial Windows preparation.
12 Peter Norton’s QuickC for Windows
Make sure the QuickWin EXE option is selected, and click the OK button.
Next, choose the Go item in the Run menu, as shown in Figure 1-8. QCWin
compiles the file, and runs it in its own window, as shown in Figure 1-9.
As you can see, this looks much like the DOS version. In fact, you can accept
input using scanf( ) as well as using printf( ), just as you can under DOS. Now,
however, it’s running in Windows.
i r UUUU IU 1 J
Continue to Cursor F7
^include <stdio.h>
Trace Into F8
main() Step Over FI 0
{ Animate
printf("Hello,
return(0);
}
U
Run the Program 00005 061
Note that one of the advantages of using the QuickWin option is that you
can run normal DOS-type C code but still have access to the tremendous
amount of memory that Windows supplies. However, you must run
QuickWin .exe files in Windows Standard or Enhanced modes, not its real
(nonprotected) mode.
That’s one way to get your program running under Windows. However, this is
really just the same as running your program under DOS; everything is still
very line oriented, and we can’t take advantage of what Windows has to offer
us. Let’s turn now to seeing how to produce a window just like the one in
Figure 1-9 as a true Windows .exe file.
We can begin by starting QuickCase:W. Just double click its icon, and a win¬
dow appears as in Figure 1-10. Welcome to QuickCase:W, a tool that will save
us hours of design time (and can actually be fun to use). The window that
we’re actually designing appears in QuickCase:W’s client area, and, as we
make changes, we’ll see them take immediate effect. In this first example,
we’re not going to do anything special; in fact, we’ll hardly do anything at all.
QuickCase:W - (Untitled)
File EMi Design Build Tools Options Help
IF Your Window's
._ Title Goes
_ Here
.. _ _ _ _ 33
i
«>> Your Window's Menu Goes Here
QuickCaseiW at Work
QuickCaseiW’s job is to generate the C code (and other associated files) that
will produce the window we want. When it generates this code, it will fill a .win
file with the development information that QuickCaseiW uses. To create a
.win file, open the File menu and select the Save As... item; a dialog box opens
up, asking for the name you wish to use for the .win file. In our case, we might
call this hello.win; type this name in and click OK. Now we’re ready to let
QuickCaseiW do some software engineering for us. In fact, QuickCaseiW can
even comment the program it writes for us. In the Options menu, choose the
Comments... item, and a dialog box opens, giving us three levels of comment¬
ing! Low, Moderate, and High. Since this is our first Windows program,
choose High so we can get as many comments as possible.
Next, open QuickCaseiW’s Build menu, as shown in Figure 1-11. There are
two options here: Generate and Update. When we first create a program, we’ll
use the Generate option to generate the corresponding code. Then we’ll
modify that code ourselves, adding to it and customizing it to do what we want.
If we want to make changes to the visual interface, however, we’ll go back to
QuickCaseiW- (HELLO.WIN)
Fi le iv.dil Design Build 5 Options Help
;
If Generate
Update
i ziJ
QuickCase:W - (HELLO.WINJ ID
Tools Options Help
p J 3j
Generate
the .win file we created and make the changes in QuickCase:W. On the other
hand, we don’t want to generate all new code then because we’ve already
modified the last version of our program (sometimes extensively). In this case,
QuickCase:W allows us to update our old code with the Update option; here it
will change only those aspects of the interface that we’ve changed ourselves,
and leave the code that we’ve already added alone.
/***********************************************************
/*
Windows 3.0 Main Program Body
/*
The following routine is the Windows Main Program. The
-J. , -1——.1 i. --J i
?nr
00001 001
We’ll see more about that aspect of QCWin programming later; for now,
select the Generate option. When you do, the generating icon appears on the
screen as shown in Figure 1-12; QuickCase:W is now writing our program for
us. After a while, a new window appears on the screen with the message:
Generation of hello.win is complete.
Now our C code is ready for us to work on, and it’s called hello.c. We can edit
it in the QuickC development environment; double click the QCWin icon to
open the development environment up as before (Figure 1-4). Now load in
the file hello.c with the Open... item in the File menu. The file we’ve created,
hello.c, appears in a window, as in Figure 1-13 — and also in Listing 1-1. As
you might notice, it’s very long, which is typical of Windows programming.
/' */
/* Windows 3.0 Main Program Body */
/* /
/* The following routine is the Windows Main Program. The Main Program */
/* is executed when a program is selected from the Windows Control */
/* Panel or File Manager. The WinMain routine registers and creates */
/* the program's main window and initializes global objects. The */
/* WinMain houtine also includes the applications message dispatch */
/* loop. Every window message destined for the main window or any */
/* subordinate windows is obtained, possibly translated, and */
/ ************************************************************************
{
/***********************************************************************/
strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
{
/* registering one of the windows failed */
sizeof(szString));
return nRc;
}
}
hWndMain = CreateWindow(
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
return IDS_ERR_CREATE_WINDOW;
{
TranslateMessage(&msg);
DispatchMessage(&msg);
CwUnRegisterClasses();
return msg.wParam;
} /* End of WinMain */
y'************************************************************************/
/* */
/* Main Window Procedure */
/* */
/* This procedure provides service routines for the Windows events */
/* (messages) that Windows sends to the window, as well as the user */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding */
/* keyboard accelerators. */
/* */
/* The SWITCH statement shown below distributes the window messages to */
/* the respective message service routines, which are set apart by the */
/* CASE statements. The window procedures must provide an appropriate */
/* service routine for its end user initiated messages, as well as the */
/* general Windows messages (i.e., WM_CLOSE message). If a message is */
/* sent to this procedure for which there is no programmed CASE clause */
/* (i.e., no service routine), the message is defaulted to the */
/* DefWindowProc function, where it is handled by Windows */
/* */
/* For the end-user initiated messages, this procedure is concerned */
/* principally with the WM_COMMAND message. The menu control ID (or the */
/* corresponding accelerator ID) is communicated to this procedure in */
/* the first message parameter (wParam). The window procedure provides */
/* a major CASE statement for the WM_COMMAND message and a subordinate */
/* SWITCH statement to provide CASE clauses for the message service */
/* routines for the various menu items, identified by their ID values. */
/* */
/* The message service routines for the individual menu items are the */
/* main work points in the program. These service routines contain the */
/* units of work performed when the end user select one of the menu */
/* controls. The required application response to a menu control is */
/* programmed in its associated CASE clause. The service routines may */
/* contain subroutine calls to separately compiled and libraried */
(continued)
20 Peter Norton’s QuickC for Windows
/* routine when Windows recognizes the end event and sends a WM_COMMAND */
/* /
^************************************************************************ /
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; / * handle for the menu */
HBITMAP hBitmap=0; / * handle for bitmaps */
HDC hDC; / * handle for the display device */
PAINTSTRUCT ps; / * holds PAINT information */
int nRc=0; / * return code */
switch (Message)
rl
case WM_.CREATE:
/* The WM_CREATE message is sent once to a window when the */
/* window is created. The window procedure for the new window */
/* receives this message after the window is created, but */
/* before the window becomes visible. */
/* */
/* Parameters : */
/* */
/* lParam Points to a CREATESTRUCT structure with */
/* the following form: */
/* */
/* typedef struct */
/* { */
/* LPSTR . lpCreateParams; */
/* HANDLE hlnst; */
/* HANDLE hMenu; */
/* HWND hwndParent; */
/* int cy; */
/* int cx; */
/* int y; */
/* int x; */
/* LONG style; */
/* LPSTR IpszName; */
/* LPSTR IpszClass; */
Welcome to QuickC for Windows 21
/* DWORD dwExStyle; */
/* } CREATESTRUCT; */
default:
/* B’or any message for which you don't specifically provide a */
(continued)
22 Peter Norton’s QuickC for Windows
return OL;
} /* End of WndProc */
/ ************************************************************************/
/* */
/ * nCwRegisterClasses Function */
* */
/
/ * The following function registers all the classes of all the windows */
/ * associated with this application. The function returns an error code */
/ * if unsuccessful, otherwise it returns 0. */
/* * /
/ ************************************************************************ /
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************i
void CwUnRegisterClasses(void)
Welcome to QuickC for Windows 23
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
QuickCase:W has produced this code for us (and we’ll spend some time going
through it in depth in the next chapter). In fact, this large program is the
minimum required to get a single blank window on the screen! We want to
modify the code ourselves (i.e., to print out “Hello, world.”), so let’s look at an
overview of the program now. There are two major functions in this program,
WinMain( ) and WndProc( ):
hello.c
WinMain( )
WinProc( )
The WinMain( ) function sets up the window and displays it. From then on,
WndProc( ) takes over. In standard Windows applications, it is WndProc( )
that handles responses from the user, such as button pushes or menu selec¬
tions. It does that by receiving Windows messages.
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
24 Peter Norton’s QuickC for Windows
We should note several things here; first, the function returns a long value, is
declared FAR, uses the PASCAL calling convention, and has a number of
parameters. The first parameter, hWnd, is a window handle that will tell us (as
we’ll see later) which window the message is for. This is because one window
procedure like WndProc( ) can handle messages for many windows, and each
of these windows are identified by their own handles.
The next parameter is the message itself, one word long. This parameter holds
values that are defined in the include file windows.h that comes with the
QCWin package. For example, depending on what occurred, these are the
kinds of messages that will be passed to us by Windows (there are actually
about 150 such messages):
The prefix WM stands for Window Message. The usual procedure in a window
procedure like WndProc( ) is to set up a switch statement, and to take the
appropriate action depending on which message was sent. The next two pa¬
rameters, wParam and IParam, hold data that is important for this message;
wParam is one word long, and IParam is two words long. For example, if a
window is moved, these parameters hold its new location.
The important WndProc( ) event for us here, in our first program, is the
WM_PAINT event. This is a request from Windows for us to draw the client
area of our window. A WM PAINT message is issued when our window is first
displayed, and every time therafter when it needs to be redrawn. For example,
another window may be moved over ours on the screen; when ours is uncov¬
ered again, Windows will send our WndProc( ) function a WM_PAINT mes-
Welcome to QuickC for Windows 25
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
SetBkMode(hDC, TRANSPARENT);
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
26 Peter Norton’s QuickC for Windows
As you can see, QuickCase:W has given us a switch statement so we can check
which message was sent to us, and therefore what the user is asking us to do.
Besides the ones listed, however, we should know that there are many other
Windows messages possible, such as WM_LBUTTON, which indicates that the
left mouse button was pushed; WM_KEYUP, which indicates that the struck
key was released; or WM_HSCROLL, which indicates that the user used the
window’s horizontal scroll bar. We’ll see more about the variety of such mes¬
sages later; for now, let’s take a look at the ones QuickCase:W put into our
program.
)
The first message in the switch statement is WM_CREATE, and the code that
we would normally put there is code that we want to execute when the window
is first created (before it is displayed). Initialization code typically goes there;
that is, we might want to initialize some variables there so that they can be
used by the rest of our program. We should note that WndProc( ) is called
whenever messages are sent to us, which means that if we want the values of
any of our variables to be retained between calls, we’ll have to declare them
static. For example, we might want to store a value in an integer named
my_int; if we wanted it to hold that value between messages, we’d have to
declare it static like this:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
switch (Message)
{
case WM_CREATE:
break; /* End of WM_.CREATE */
The next two messages in our window procedure’s switch statement are
WM MOVE, and WM_SIZE. We’ll get these messages when the window has
been moved or (re)sized. The window’s new location or dimensions will be
passed in the two additional parameters that are always passed to us: wParam
and lParam (and we’ll see a great deal of these two throughout this book).
Welcome to QuickC for Windows 27
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG iParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap= 0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
SetBkMode(hDC, TRANSPARENT);
EndPaint(hWnd, &ps) ;
break; /* End of WM_PAINT */
invalid
28 Peter Norton’s QuickC for Windows
One easy way of fixing a window without worrying about exactly which part
of it is invalid is to redraw the whole thing (if that doesn’t take too much
time). Windows will restrict all drawing attempts to the invalid rectangle
and drawing outside it will simply have no effect.
The code QuickCase:W has written for us in the WM_PAINT case starts by
clearing the fields of a paint structure it has defined earlier as ps:
Next, it fills that paint structure with a call to the Windows function
BeginPaint( ). Note that we pass it the handle of our window so that Windows
will know which window’s invalid rectangle we’re requesting information
about and the address of our paint structure:
This way, the BeginPaint( ) function fills the paint structure; the fields of that
structure look like this:
Note in particular the third field, which is of type RECT. This is how Windows
passes the dimensions of the invalid rectangle to us, in a structure of type
RECT, which stands for rectangle. That structure is defined this way:
The top left corner of our invalid rectangle has the coordinates (left, top) and
the bottom right corner has the coordinates (right, bottom). These coordi¬
nates are measured in pixels from the top left of our window, which is the
origin, (0,0), like this:
o
x
y
le ft
LIE U
invalid
bottom
right
This is a handle to the invalid rectangle and allows us to draw in it. It might
seem strange to have a second handle like this — after all, we already have a
handle to our window — but this is a handle to the invalid rectangle and
all drawing operations must be performed with this handle, hDC, not hWnd.
The next call that QuickCase:W has put in for us uses hDC like this,
SetBkMode (hDC, TRANSPARENT) in the following example:
30 Peter Norton’s QuickC for Windows
NOTE hDC stands for Device Context handle, and we’ll see more about device
contexts in the next chapter.
This tells Windows that we’re setting the background mode to transparent in the
invalid rectangle (TRANSPARENT is defined in windows.h). Usually, Win¬
dows draws text in its opaque mode, which means that whatever goes on the
screen erases whatever’s underneath. Here, however, we’re setting the mode
to transparent, which will allow us to overwrite previous characters (for exam¬
ple, we can add underscores for underlining this way).
This point in the program is also where we’ll be able to add our own code to
print out “Hello, world.” QuickCase:W has written hello.c for us, which is 266
lines long; now we’ll add two lines (one of which will be a comment) to put
our message on the screen and complete the program. To do that, we can use
the Windows function TextOut( ), which is the printf( ) of Windows, along
with the handle we have to the invalid rectangle, hDC.
SetBkMode(hDC, TRANSPARENT);
TextOut( ) takes five parameters: The handle of the rectangle we’re drawing
in, hDC; the x (horizontal) and y (vertical) coordinates of our message, which
will be (0, 0) in our case; a far pointer to our message itself that we pass as
(LPSTR) “Hello, world.”; and, finally, the length of the string to print, which
we find with strlen ( ). (The LPSTR type override converts a near pointer to a
string into a far one, and is defined in windows.h.)
That’s it; executing this line of code places our message on the screen, in our
window. To see it, make the above changes to the WM_PAINT case of hello.c
in the QCWin development environment, and choose the Go item in the Run
menu. That’s all there is to it; the window with our message in it appears on
the screen, as shown in Figure 1-14. Congratulations: You’re a Windows pro¬
grammer. And we’ve displayed our message from a true Windows program.
Notice that the window can be resized and moved around the screen, and we
also have min and max buttons, as well as a system menu. You might also
notice that, without QuickCase:W, we would have been forced to write hun¬
dreds of lines of code. As it was, we only had to write two lines, which, in its
way, is about as much work as the DOS program we saw earlier:
#include <stdio.h>
main()
{
printf("Hello, world.");
return(0) ;
}
It’s important to use EndPaint( ) for every message in which you’ve used
BeginPaint ( ). If you don’t, your Windows program is going to keep allocat¬
ing new handles to invalid rectangles, and the program will crash.
That’s it for the WM PAINT message. The next message that we handle in
WndProc( ) is WM_CLOSE, which is sent to us when our window is being
closed (e.g., when the user selects the Close item in our window’s system
menu):
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG IParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc = 0; /* return code */
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
SetBkMode(hDC, TRANSPARENT);
/* USER-ADDED CODE */
Welcome to QuickC for Windows 33
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
/* End of WndProc */
We take a window off the screen with a call to DestroyWindow( ) like this
(keep in mind that hWnd is the handle of the window that was passed to us):
—> DestroyWindow(hWnd) ;
This destroys the window. Note that, because a Windows application can actu¬
ally use many windows besides the main one (the others are called child win¬
dows), we may have received a WM_CLOSE message for one of those. If,
however, we’ve received a WM_CLOSE message for the main window, then we
want to end the application as a whole, which we do like this with
PostQuitMessage ( ):
As we’ll see in the next chapter, our program has already stored the main
window’s handle in the variable hWndMain (in our case, it’s the same as
hWnd since there’s only one window in our program). If that’s the window
we’re being asked to close, then the user is ending our program. The correct
way to end a Windows program is with PostQuitMessage ( ), which sends a
special WM_QUIT message. As we’ll see in the next chapter, this is one mes¬
sage that is actually handled by WinMain( ), not WndProc ( ). We also pass a
34 Peter Norton’s QuickC for Windows
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC ; /* handle for the display device */
PAINTSTRUCT1 ps; /* holds PAINT information */
int nRc=0; /* return code */
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
SetBkMode(hDC, TRANSPARENT);
/* USER-ADDED CODE */
TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
—> default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return OL;
} /* End of WndProc */
Welcome to QuickC for Windows 35
In fact, we won’t handle the majority of the thousands of messages that usually
come through our code. Instead, we’ll pass them on to the Windows default
window procedure DefWindowProc ( ), which takes the same parameters as
were passed to us in WndProc( ). In this case, the value we return from
WndProc( ) will be the value that DefWindowProc ( ) returns to us. On the
other hand, if we handle the message ourselves, we’ll return a value of 0 (see
the last line of WndProc( ) above), which means that the message was handled
correctly.
All messages, even system messages, for our windows will come through a
window procedure like this. Most of them will be sent to
DefWindowProc( ), but even so, we have the responsibility of making sure
that they get there. This gives us a great deal of power in Windows; as you
become more more proficent, you might even want to start working with
messages that Windows itself usually handles.
Now that we’ve actually put together our first working Windows program —
with help from QuickCase:W — we should spend the time to see what makes
it tick. The reason for this is that QuickCaseiW only generates the interface
code for us; that is, a shell of a program. We’re completely responsible for
modifying it the way we want it. And because we are, we have to know what’s
going on inside it.
In this chapter, we’ll find that QuickCase:W and the other tools that come
with QuickC for Windows hide many of the details from us. For example,
we’ve only really worked on one file so far — hello.c — but in fact there are
six files that were necessary to get as far as we did, and some of our programs
will have even more. We’ll also find that a Windows program is actually a
complex thing, and much of what follows will be about wading through
masses of sometimes tedious details — but without question (and tedious as it
may be), all of the material in this chapter has to be mastered by C program¬
mers before they become Windows programmers. So, let’s start immediately,
learning just what makes a Windows program tick.
37
38 Peter Norton’s QuickC for Windows
Hungarian Notation
We should begin our exploration of Windows programming by understand¬
ing the naming convention of Windows variables. You may have noticed that
many of the variables in our program in the last chapter began with lower case
characters, like hWnd or hDC. In fact, this is a Windows convention called
Hungarian notation (named for its Hungarian inventor, programmer Charles
Simonyi, a Microsoft programmer). Because Windows programs can be so
long, it’s easy to lose track of what all the variables mean. To help, Hungarian
notation provides letters that can be used as prefixes, and they appear in
Table 2-1 for reference (which you might refer to frequently throughout this
book). They can also be combined; for example, IpszMyString means a long
pointer to a zero-terminated string named MyString.
Prefix Means
a array
b bool (int)
by unsigned char (byte)
c char
cb count of bytes
cr color reference value
cx cy
dw unsigned long (dword)
fn function
h handle
i integer
n short or int
np near pointer
P pointer
1 long
ip long pointer
s string
sz string terminated with a zero
tm text metric
w unsigned int (word)
x>y short (x or y coordinate)
This naming convention can help a great deal, so let’s put it to work at once
by deciphering our windows program, hello.c.
hello, c
WinMain( )
WinProc( )
We’ve spent some time in the last chapter examining WndProc( ) already, and
we know that that’s the part of the code that handles the messages we get from
Windows. Since a Windows program is exjent-driven—i.e., we wait for events
like button pushes or menu selections — we have to wait for Windows to send
us the appropriate messages that match the events that have occurred. These
messages stack up in a message queue (internal to Windows), and we read them
one at a time, handling them in order. For example, if the user moved our
window and then pressed a key, Windows would call WndProc( ) with a
WM MOVE message, followed by a WM KEYDOWN message.
It turns out that one of the primary jobs of WinMain( ) is to set up this
message queue for us, informing Windows that we want our programs mes¬
sages sent to WndProc( ). In addition, it sets up the window(s) we want in the
first place and displays it (or them) on the screen. In fact, when our program
is first loaded, WinMain( ) is called first by Windows — this is how our pro¬
gram gets started. For that reason, let’s examine the structure of hello.c’s
WinMain( ) now. It starts by including a special include file created for us by
QuickCase:W, hello.h, and by declaring WinMain ( ), as well as indicating what
parameters are passed to it (from Windows):
40 Peter Norton’s QuickC for Windows
At this point, we’ve got the chance to put Hungarian notation to work. We can
see that the first two parameters, hlnstance and hPrevInstance, are handles of
some type, meaning that they’re one word numbers standing for some Win¬
dows programming construct. In this case, hlnstance is our application’s in¬
stance handle, which is a number that identifies our job. Because Windows is a
multitasking environment, there may be a number of instances of our program
running, and hlnstance is the unique handle for ours.
The next parameter, hPrevInstance, indicates whether or not there are other
instances of our program running. If hPrevInstance is equal to NULL (which
is defined in Windows.h as 0), then there are no other copies of our program
running. If it is nonzero, however, then there are other copies of our program
running, and advanced programs can take advantage of that by sharing data
areas in memory. In addition, some programs may require the use of re¬
sources that absolutely cannot be shared, such as some hardware component,
in which case we can design WinMain ( ) to quit if another copy of our pro¬
gram is already running (and therefore using that nonshareable resource).
be “ novel.txt”, 0. (It’s also possible to start programs and pass command lines
like this from the Run... dialog box of either the Windows File Manager or the
Program Manager.)
You may wonder where the program variables szAppName and hlnst are de¬
fined, and the answer is that they are in hello.h, which QuickCase:W created
for us. szAppName is a string that will hold the name of our application —
which is HELLO, from hello.c — and hlnst is going to hold a copy of our job’s
instance handle, hlnstance. This is how they are defined in hello.h (HWND is
a window handle type, as opposed to the more generic handle type,
HANDLE):
#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2
—»HWND hlnst;
HWND hWndMain;
After the variables we need are set up, the next step is to register the kind of
window we want with Windows. That is, our object now is to display the win¬
dow we designed in QuickCase:W, and we have to let Windows know just how
that window was designed. There are many, many different ways of designing
windows (e.g., with or without scroll bars, with or without system menus, with
or without borders, and so on), and to tell Windows what ours should look
like, we’ll have to register it.
strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance) <—
{
/* register window classes if first instance of application */
}
}
hello.c
WinMain ( )
WinProc( )
CwRegisterClasses( )
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
Now we have to load the fields of wndclass. QuickCase:W has taken care of the
details for us here, but it’s important to know what’s going on in case we want
to make modifications. We start by defining a class style from the available
options, and by telling Windows where it will find our window procedure (i.e.,
WndProc( )) — that is, where to send window messages to:
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
You might also notice that we’re setting up a long pointer to a function
(lpfnWndProc) here; that pointer points to WndProc( ), and when we pass it
to the Windows function RegisterClass( ) in wndclass, Windows will know that
it should send window messages for this class to our function WndProc( ).
This is how we connect WndProc( ) to Windows (WinMain( ), on the other
hand, is called automatically when the program begins):
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
NOTE Only one copy of WndProc( ) will be registered for a class (since you only
register a class once), which means that only one copy of WndProc( ) will
be handling all messages for all instances of your Windows program. If
multiple instances of your program are running, WndProc( ) will keep
them separate because they each have different window handles, hWnd
(which is one of the parameters passed to it).
Next in filling wndclass, we have the option of setting up. extra memory space
in the class for our own use. Windows programs occasionally make use of this
extra space if they’re going to have many windows of the same class — in
particular, they can store information that distinguishes each window from
the others there. In our case, however, we’re not going to ask for any extra
memory space, so we’ll set these parameters to 0.
After that, we have to pass the instance handle for this job, which is hlnstance,
and choose an icon for our application, as well as the shape of the mouse
cursor. We do that like this:
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
□Hello
We’ll also see that it’s possible to design our own application icon or cursor
using the Image Editor that comes with QuickC for Windows.
Next, we’re responsible for setting up the background color; setting up any
menus we might have; and selecting a name for our window class. To paint a
window’s background, Windows uses what it calls a brush, as we’ll see when we
cover graphics. That means that it is actually expecting a handle to a Windows
brush, and we can add that like this:
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
wndclass.cbWndExtra = 0;
wndclass.hlnstance = hlnst;
wndclass.hlcon = Loadlcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
/* Create brush for erasing background */
wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW+l); <—
Next, we must indicate what menu(s) if any, we’re going to use, and what the
name of our window class is going to be. Because our window does not have its
own menu, we could simply set the corresponding field to NULL (i.e., like
this: wndclass.IpszMenuName = NULL) if we had wanted to. However, as a
default, QuickCase:W sets this field equal to the application’s name, as stored
in the string szAppName (that is, wndclass.lpszMenuName = szAppName;).
When the program runs, it will find that in fact there is no menu associated
with this name, and will therefore not place one in our window.
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance)
{
50 Peter Norton’s QuickC for Windows
The LoadString ( ) function is one of several Windows functions that are spe¬
cially designed to load resources that you have designed into your program.
(Other such functions include: LoadMenu( ), LoadCursor( ), LoadIcon( ),
and LoadBitmap( ).) That is, resources include such items as menus, cursors,
icons, and bitmaps, and information about them goes into a resource file, which
has the extension .rc. For example, hello.rc (created by QuickCase:W) looks
like this:
#include "HELLO.h"
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END
Note that we only have two strings defined here; there is nothing indicating
anything about icons, menus, or cursors because we haven’t defined any of
those for our program. As our programs become more complex, we’ll see
more information here about such resources — but even then, QuickCase:W
will handle most of the details for us. In this case, we’re interested in the
second string stored in our resource file, IDS_ERR_REGISTER_CLASS (IDS
stands for ID for a string). You may recall that the constant IDS_ERR_REGIS-
TER_CLASS was defined in hello.h (also produced by QuickCase:W) as the
numeric constant 2:
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END
strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
52 Peter Norton’s QuickC for Windows
Then we use the Windows function MessageBox( ) to place that error message
(“Error registering window class”) in a message box on the screen.
MessageBox ( ) is an extraordinarily useful Windows function; it allows you to
communicate simple messages to the user and receive some response
(through buttons like OK or Cancel) without the (sometimes) long, involved
process of designing your own window to do so. We’ll see more about this
function when we start working with dialog boxes later. After we display the
error message, we quit WinMain( ), returning the error code we got from
nCwRegisterClasses( ) (i.e., -1) as the termination code of the entire program.
That’s it for the case in which there was an error; now let’s look at the case in
which everything went as planned.
Creating a Window
If we were successful in registering the window class, then it’s time to create
our own window of this class, preparing ourselves to actually display it. We
create a window in WinMain( ) with Create Window ( ), and then can display it
with ShowWindow( ). Note that these are two distinct steps — creating a win¬
dow does not display it on the screen; instead, we must use ShowWindow( ) to
do that.
It’s often useful to work on a window, creating graphics and so on, before
displaying it. In that case, you would create it, draw whatever you wanted on
it as we’ll see how to do later, and then show it.
The most important parameter in the call to Create Window ( ) is the first, in
which we indicate the name of the class of window we want to use. In our case,
that’s HELLO, as stored in szAppName:
Now we’ve indicated what class Windows should use to create our new win¬
dow. Next, we indicate that there is no title (i.e., window caption) by passing
NULL as the following parameter; if we had wanted to give our window a title,
we could have simply placed a string here instead of NULL (e.g., “The Hello
Application”).
In addition, we get a chance to further customize our window with the use of
special window style flags. In our case, we’ll ask for a title bar to be included
(window style: WS_CAPTION); a system menu (WS_SYSMENU); a minimize
box (WS_MINIMIZEBOX); a maximize box (WS_MAXIMIZEBOX); and a
standard thick frame which can be resized — that is, different from other
available frames such as those used for dialog boxes (WSTHICKFRAME).
Finally, we’ll ask Windows not to draw in any other windows that our applica¬
tion may have — called child windows — when it paints our main window
(WS_CLIPCHILDREN), and we’ll indicate that our window may be over¬
lapped by other Windows applications’ windows (WS_OVERLAPPED). All of
these flags are more or less standard, and all of them appear in Table 2-2. To
54 Peter Norton’s QuickC for Windows
strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
Next, we are responsible for indicating our window’s location and size. These
measurements are in pixels, and the coordinate system used is the whole
screen like this:
56 Peter Norton’s QuickC for Windows
(0,0) x
In our case, we’ll leave the placement and size of our window up to Windows
by using CW_USEDEFAULT for these parameters (CW is a special prefix used
only for constants that we pass to CreateWindow( ), and stands for Create
Window). However, you can modify them yourself, indicating where you want
the window to appear. (We’ll also see that you can do the same thing — and
easier — in QuickCase:W.)
After that, we can indicate whether or not this is a child window of some other
window. Doing that gives the other window control over this window and
connects them together. To indicate that this window is the child of another
window — called the parent window — we could pass the parent window’s han¬
dle (like hWnd) as the next parameter. However, the window we’re creating is
not the child of any other window, so we pass NULL here. Similiarly, the
parameter after that allows us to install a menu system if we want to use one;
however, we don’t in this case, so we pass another NULL. Finally, we pass the
instance of our job (hlnst), and one last NULL to indicate that we don’t want
any creation parameters added, which is data that we install ourselves and could
access when the window is being created (i.e., when a WM CREATE message
is sent to our window procedure, WndProc( )):
if(hWndMain == NULL)
{
—» Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
—>
—> MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
If there was no error, however, our window is created and now exists, and
Create Window ( ) has returned a handle to it, which we store in the variable
hWndMain (indicating that it is the handle of the main window). Note that
our window does not yet appear on the screen; we’re responsible for that
ourselves.
At this point, you must have some idea of the amount of detail that
QuickCase:W has already handled for us. Using QuickCase:W, we simply had
to draw what we wanted on the screen, and it created the code for us. We’ll see
later that we can incorporate even more items — like dialog boxes — into
QuickCase:W also, and it will still handle all the details of programming the
interface. That is, the whole idea behind QuickCase:W is that it will handle
most of the programming for the interface, and we’re responsible for the rest.
(In this way, the actual C programming that we have to do is often not much
more than we’d have to do under DOS.)
The next step in WinMain( ) is simply to show our window (since we have no
special preparation to perform before displaying it), and we do that with a
simple call to ShowWindow( ). That turns out to be easy now that our window
actually exists; we just pass the window’s handle, hWndMain, and the parame¬
ter that indicates how the window should first appear, nCmdShow. This pa¬
rameter, nCmdShow, indicates whether Windows wants our program to start
Anatomy of a Windows Program 59
And that means that the call to ShowWindow( ) looks like just this:
At last, our window is actually on the screen. You might think that we’re done
with WinMain( ) now — after all, the window is created and displayed, and
we’ve already passed the address of our window procedure, WndProc( )
(which handles the window messages) to Windows. In fact, however, we’re
responsible for sending those messages on their way ourselves, as well as
performing some preliminary processing on them. It turns out that we’re only
finished with the first part of WinMain ( ) —the registering, creating, and
window displaying part. The next (and shorter) part is the message loop part.
60 Peter Norton’s QuickC for Windows
/* Message structure */
typedef struct tagMSG
{
HWND hwnd ;
WORD message;
WORD wParam;
LONG IParam;
DWORD time ;
POINT pt;
} MSG;
The first field of this structure holds the handle of the window that this
message is targeted for; each message can go to only one window. The next
field holds the message itself, encoded in predefined constants like WM_SIZE,
WM_PAINT, or WM_MOUSEMOVE. The following two fields hold the two
data items associated with each message, wParam and IParam (and we’ll see a
great deal more about these throughout the book). After that, the time is
encoded in a field called time, and the field named pt holds the coordinates
of the mouse cursor at the time the message was generated. We might note
that pt is itself a structure of the POINT type, which, in Windows, looks like
this:
To make sure we get all the messages that come to us, we now enter the
message loop in WinMain( ). To get a message in the first place, we call the
Windows function GetMessage( ). This function fills a message structure and
returns a value. The return value is nonzero (i.e., logically true) if the message
we just got was anything but WMQUIT, which is the final message Windows
applications receive. If we get a WMQUIT message, we want to leave the
message loop and finish up with WinMain( ) (i.e., there will not be any more
messages to receive), so we can set up our message loop like this (recall that
WM QUIT makes GetMessage( ) return a 0 value, which terminates the
loop):
Anatomy of a Windows Program 61
Now, we have to pass the msg structure back to Windows itself for some
preliminary translation. We’ve already seen that when a key is pressed, a
WM_KEYDOWN message is generated. However, as we’ll see in the next chap¬
ter, it’s a little difficult to extract information — such as what letter was actu¬
ally typed, and whether or not it’s a capital or small letter — from a
WM_KEYDOWN message. Instead, we can call TranslateMessage( ) to trans¬
late the information in a WM_KEYDOWN message and to generate a
WM_CHAR message, which passes the ASCII code of the struck key on to us
directly (in the parameter wParam). We use TranslateMessage( ) like this in
the message loop:
}
62 Peter Norton’s QuickC for Windows
At this point in our program, we’re ready to pass the message on to our
window procedure, WndProc( ), and we do that simply with DispatchMess-
age ( ), like this:
{
TranslateMessage(&msg);
—> DispatchMessage(&msg);
}
In this way, we keep looping over messages in the message loop, continually
forwarding them on to WndProc( ) until we receive a WM QUIT message, at
which time we leave the message loop and exit WinMain( ), ending the pro¬
gram. In other words, the flow of control looks something like this, where we
send all messages except WM QUIT to WndProc( ):
hello.c
WinMain( )
Message Loop:
Windows messages
o
WndProc( )
WM_QUIT-——►Exit
switch
CwRegisterClasses( )
Note that handling the messages ourselves this way gives us considerable free¬
dom concerning which message we pass on to WndProc( ) (i.e., which mes¬
sages we handle and which we don’t). Keep in mind, however, that system
messages which are directed towards windows also come in through this mes¬
sage loop, and we have to pass them on to WndProc( ) so that they can be
handled by the default window procedure, DefWindowProc( ) at the end:
Anatomy of a Windows Program 63
hello.c
WinMain( )
Message Loop:
Windows messages Exit
=o WMQUIT
WndProc( )
switch
DefWindowProc ( )
CwRegisterClasses( )
This is pretty much the standard form for message loops to take, but there will
be some refinements made to it later in this book. For example, when we add
accelerator keys to our menus (i.e., the Ctrl+Key method of invoking a menu
command), we’ll have to check for such keys before calling Translatemess-
age ( ) — which would simply convert them to ASCII and send them on to
WndProc( ) instead of sending the correct Windows message. Also, if we allow
the user to keep a dialog box open while still working with the main window
(called a nonmodal dialog box), we’ll find that the message loop must be
modified so that messages intended for the dialog box get sent there and not
to our main window with DispatchMessage( ). (In fact, QuickCase:W will han¬
dle such programming details for us automatically, as we’ll see.)
First, we destroy the window, then check if it’s the main window (i.e., is hWnd,
which is passed to WndProc( ), equal to hWndMain?). If so, we use the Win¬
dows function PostQuitMessage ( ) to send a WM_QUIT message to ourselves
(that is, to the message loop in WinMain( )). This is the only message that we
do not pass along to WndProc( ); instead, GetMessage( ) returns 0 here (i.e.,
logical false), so control leaves the message loop in WinMain( ) and drops
through to the next lines:
• »
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
CwUnRegisterClasses() ; <—
hello.c
WinMain ( )
Message Loop:
Windows messages Exit
WndProc( )
switch
DefWindowProc( )
CwRegisterClasses( )
CwUnRegisterClasses( )
Anatomy of a Windows Program 65
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
Finally, in the last line of WinMain( ), we return a termination code. This was
the code passed to us from PostQuitMessage ( ) back in WndProc( ); in our
case, we passed a 0 (i.e., PostQuitMessage (0)), so the wParam parameter
associated with the WM_QUIT message will be zero, and we return that as our
termination code:
And that’s it for WinMain( ); the whole function appears in Listing 2-1 (note
that all of hello.c — including WinMain ( ), WndProc( ), cwRegisterClasses( ),
and CwUnRegisterClasses ( ) — appears in the previous chapter, Listing 1-1).
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR__CREATE_WINDOW;
}
************************************************************************ /
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/****************************************************************■*•*******/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses V
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
(continued)
68 Peter Norton’s QuickC for Windows
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
User
Anatomy of a Windows Program 69
And we already know how WndProc( ) works; we just process messages there
in a large switch statement, like this:
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return OL;
}
The entire listing of WndProc( ) appears in Listing 2-2 for reference. That’s it,
then, for the C code for our program. However, as mentioned in the begin¬
ning of the chapter, there are a number of auxiliary files that we need to
generate Hello.exe. We’ve already seen two of them: 1) hello.h, a header file
which declares some variables such as szAppName[], some constants like
IDS_ERR_REGISTER_CLASS, and function prototypes; and 2) hello.rc,
which contains the two string resources that we use:
#include "HELLO.h"
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, Window creation failed!"
IDS_ERR_REGISTER_CLASS, Error registering window class
END
70 Peter Norton’s QuickC for Windows
switch (Message)
{
case WM_CREATE:
/* The WM_CREATE message is sent once to a window when the */
/* window is created. The window procedure for the new window */
/* receives this message after the window is created, but */
/* before the window becomes visible. */
/* */
/* Parameters: */
/* */
/* lParam - Points to a CREATESTRUCT structure with */
/* the following form: */
/* */
/* typedef struct */
/* { */
/* LPSTR lpCreateParams; */
/* HANDLE hlnst; */
/* HANDLE hMenu; */
/* HWND hwndParent; */
/* int cy; */
/* int cx; */
/* int y; */
/* int x; */
/* LONG style; */
/* LPSTR IpszName; */
/* LPSTR IpszClass; */
/* DWORD dwExStyle; */
/* } CREATESTRUCT; */
/* USER-ADDED MESSAGE */
TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, \
world."));
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
QuickCase.W
NOTE As we’ve seen, QuickCaserW also generates another file, hello.win, for its
internal bookkeeping. If we want to update hello.c later, QuickCase:W will
refer back to this file.
We’ve already seen the first three files here: hello.c, hello.rc, and hello.h. The
next file, hello.def, is used in the creation ofhello.exe, and holds instructions
primarily for the linker:
NAME HELLO
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 4096
STACKSIZE 5110
EXPORTS WndProc @1
Here, we’re indicating that the name of the file we want to produce is
hello.exe; that this .exe file should be executable under Windows; and that we
want to include the stub file winstub.exe inside it. This stub file is the module
that prints the message: “This program requires Microsoft Windows.” Includ¬
ing winstub.exe means that users will get this message if they try to run
hello.exe under DOS.
Next in hello.def, we indicate how we want to use memory in our .exe file; in
particular, Windows has the ability to move segments of memory around as
part of its memory managment scheme, and we’re indicating that our code
and data segments are movable upon demand:
NAME HELLO
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
Anatomy of a Windows Program 73
HEAPSIZE 4096
STACKSIZE 5110
EXPORTS WndProc @1
We also indicate a required minimum size for our heap and stack (it’s always
better to ask for too much than too little when setting up the stack), and then
indicate that we want to export the window procedure WndProc ( ):
NAME HELLO
EXETYPE WINDOWS
STUB 'WINSTUB.EXE'
CODE PRELOAD MOVEABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 4096 <r~
Any procedure that Windows is expected to call in our program (with the
exception of WinMain( )) is called a call-back procedure. In our case, we ex¬
pect Windows to call WndProc ( ) when it sends us messages, so the linker has
to prepare for this by adding some additional information and code. After
processing all of this information, the linker is able to take hello.obj and
create hello.exe.
PROJ=HELLO
DEBUG=1
PROGTYPE=l
7 4 Peter Norton’s QuickC for Windows
DEFFILE=HELLO.DEF
H=HELLO.H
ALL: HELLO.EXE
HELLO.OBJ: HELLO.C
HELLO.RES: HELLO.RC
In order to work with projects, we use the Projects menu in QuickC for Win¬
dows, as shown in Figure 2-2. When you open a project, QuickC for Windows
reads the .mak file so it knows what files to use for this project. When we
request QuickC for Windows to either run our prografn or build the correct
.exe file, it will know which files it needs.
NOTE In addition to loading the correct .mak file with the Project menu, we’ll
load the files we want to edit in the QCWin environment separately, such as
hello.c, using the File menu. That is, specifying a .mak file alone does not
load in the associated files (the .c file, the .def file, and so on).
We’ll start by learning the various methods of reading keyboard input; since
this is Windows, that input will come to us through Windows messages. It turns
out that there are two different types of keyboard messages — those gener¬
ated by the action of a key (like WM_KEYDOWN and WM KEYUP) and those
generated after Windows translates such messages into ASCII for us
(WMCIIAR messages). We’ll see both in this chapter, and then we’ll see an
example program that reads input from the keyboard and displays it in a
window.
Next, we’ll work with the mouse, examining the variety of mouse-specific
Windows messages that we might receive (such as WM_MOUSEMOVE or
WM_LBUTTONDOWN), and we’ll put them to work in an example program
of their own. Note that since we’ll be displaying the results of our programs as
well as accepting input in this chapter, we’ll learn a little more about text
output as well. In particular, we’ll learn that using a variable width font (the
Windows default) can create some interesting problems for us when we want
to display and change strings in a window.
75
76 Peter Norton’s QuickC for Windows
Let’s begin, then, with an examinination of keyboard input and how to inter¬
pret it.
Also, when there are a number of buttons, listboxes, or text boxes in a window
— what Windows calls controls — the user is usually able to select which con¬
trol has the focus by clicking it with the mouse. For example, if a button has
the current focus, Windows highlights its border (which also means that the
user can choose this button by pressing the Enter key). The user might then
click a text box instead, and a flashing insertion point appears there (and the
button’s border returns to normal as it loses the focus).
Furthermore, under Windows, the user expects to be able to press the tab key
to move the focus between controls in a window. That is, if the text box we just
mentioned follows the button in the window’s tab order, then the user can
move from the button to the text box simply by pressing the tab key. In
Keyboard and Mouse Input 77
Two Windows functions that are useful here are GetFocus( ) and SetFocus( ),
which allow us to see who has the focus and set it ourselves. In addition,
when our window gets the focus, it will receive a WM_SETFOCUS message,
and when it loses it, it will see a WM_KILLFOCUS message.
In addition, certain keys usually do certain things. For example, the FI key is
usually reserved for Help. F2 is supposed to correspond to the New item in
your menus if you have one (as in New Game, New File... or New
Spreadsheet...). Alt+X is supposed to mean the same as Exit if the File menu
is open, and so on. (It’s easy to see what key conventions are standard —just
use Windows applications.)
One final point about using the keyboard from the user’s point of view is that,
under Windows, the user is supposed to be able to use the keyboard to replace
the mouse for input operations. In some cases, this is more theoretically true
than actually true (as in a graphics paint program), but in most applications,
the keyboard should be able to duplicate the mouse’s actions. When
designing Windows applications, then, we should keep that in mind.
KEYUP. We won’t work very much with the system keyboard messages, but
keep in mind that they pass through our window procedure, WndProc( ) as
well — and we’re responsible for passing them on to Windows by calling the
default windows procedure (DefWindowProc( )) at the end of WndProc( ).
All four messages are accompanied by data in the two special parameters
passed to WndProc( ): IParam and wParam:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORQ wParam, LONG IParam)
In particular, for these messages, IParam is coded this way, bit by bit (it is 32
bits long, as you can see in the above prototype, and wParam is one word
long):
transition state
r ' 1 l r~ i r i
31 30 29 • • • 24 23 . . . 16 15 . . . 00
The transition state is 0 if the key was just pressed (i.e., for WMKEYDOWN
messages), and 1 if just released (WM_KEYUP messages). The previous key
state is 0 if the key was previously up and 1 if previously down. The context
code is 1 if the Alt key is pressed; usually this is 0 for WM KEYDOWN and
WM_KEYUP, and 1 for system messages. Also, the extended flag is 1 if the
keystroke is the result of pressing or releasing one of the additional keys on
the IBM enhanced keyboard (as used with the PS/2) — note that programs
usually do not use this field.
If you keep a key pressed and generate “typematic” keystrokes, the previous
state key will be set to 1.
The OEM (OEM stands for Original Equipment Manufacturer) scan code
holds the scan code for the key as generated by the keyboard itself. For each
keystroke or valid combination of keystrokes (such as Shift-a), the keyboard
generates a unique scan code. This is the raw, untranslated information from
the keyboard port (port 60h) on the 1/O bus, and we won’t use this informa¬
tion ourselves until it’s been translated (into AuSCII) by Windows.
Keyboard and Mouse Input 79
Finally, the repeat count is an indication of typematic action. If you hold a key
down and generate automatic repetitions of that key, this field will hold the
number of repetitions. Usually, Windows does not produce a separate
W MKEYD OWN or WM_SY5KEYDOWN message for typematic action (which
would flood the message queue); instead, it bunches them together and
places a nonzero value in the repeat count field of IParam.
You might notice that we still don’t really know what key was pressed. We have
the scan code, but without a lookup table to translate to character codes, that’s
not usually much help. However, the wParam parameter contains a virtual key
code, which does tell us what key was pressed. A constant is defined for each
keystroke, as indicated in Table 3-1.
Windows also includes three mouse virtual “key” codes in its list of virtual
keys: VK_LBUTTON, VK_MBUTTON, and VKJRBUTTON, matching the
left, middle, and right keys of a mouse. However, these virtual key codes will
never be sent to us in with a WM_KEYUP or WM_KEYDOWN message
because the mouse communicates with its own set of Windows messages,
not WM_KEYDOWN or WM KEYUP. These constants are actually designed
to be passed to functions like GetKeyState( ) (which is coming up) to find
the current state of the mouse buttons — i.e., pressed or not pressed.
Note that Table 3-1 contains codes for keys that do not normally generate a
printable character, such as the function keys, VK_F1 to VK_F16, or the key¬
board arrow keys like VK_UP and VKJLEFT. And, in fact, this is the usual way
for Windows programs to read such keys. In fact, you might think that we can
read all keys this way, since even the letters (VK_A to VK_Z) are defined.
However, there is a problem. Although we can now figure out what key was
pressed, including the keys from VK_A to VK_Z, we can’t actually tell the
difference between capital and small letters like “A” and “a.” That is, if you
press “A,” wParam will hold the value VK_A— and if you press “a,” wParam
will also hold the value VK_A.
One way to resolve this difficulty is with the Windows function GetKeyState( ),
which can indicate the state of the Shift key (or any other key, including the
mouse buttons) for the message currently passed to us in WndProc( ). We use
it like this: GetKeyState(VK_SHIFT). If this value is negative, the shift key was
down when the keystroke we’re currently processing in WndProc( ) was gen¬
erated.
80 Peter Norton’s QuickC for Windows
NOTE It’s important to realize that GetKeyState( ) does not return the real-time
state of a key, but rather the state of the key at the time that the keyboard
message we’re currently analyzing in WndProc( ) was generated.
However, this method is a very clumsy way of reading keyboard input. You may
recall that when we discussed the message loop in WinMain( ) in the last
chapter, we saw a function named TranslateMessage( ), which was called for
all messages:
—> TranslateMessage(&msg);
DispatchMessage(&msg);
}
TIP Keep in mind that WM_KEYDOWN and WM_KEYUP messages will allow us
to process nonprinting keys like function keys and keys like the right or left
arrow keys.
The IParam value passed in a WM_CHAR message is the same as the IParam
value that accompanied the previous WM_KEYDOWN message.
This makes reading individual characters easy. In fact, since we already know
how to print in a window (using TextOut( )), we can put together an example
program that reads and types keys. We start by designing a simple window in
QuickCase:W as shown in Figure 3-1. Click the title bar of the window we’re
QuickCaselW - (Untitled]
File Design Build Tools Options Help
Your Window's Title Goes Here
<<>> Your Window's Menu Goes Here
Title
Window Title:
Key Input]
designing, opening the Title dialog box as shown in Figure 3-2, and giving it
the title Key Input. Next, select the Generate option in the Build menu.
QuickCase:W will ask for a name to give to this .win file; call it, say, key .win.
You can also specify the original screen size and location of your window by
using the Size and Location... item in QuickCase:W’s Design menu, which
lets you place and stretch the window on the screen until you have its
coordinates as you want them.
After QuickCase:W generates the correct files (i.e., key.c, key.mak, key.h,
key.def, and key.rc), we’re all set to start modifying them. Open the project
key.mak by selecting the Open... item in the Project menu of development
environment of QuickC for Windows, and by selecting key.mak. Next, open
the file key.c itself by selecting it with the Open... item of the File menu, as
shown in Figure 3-3.
Our intention here is to modify the program so that it can respond to key¬
strokes by intercepting the WM_CHAR message, so we have to modify the
message switch statement in WndProc( ), which currently looks like this:
Keyboard and Mouse Input 83
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
case WM_CHAR:
break;
Our goal here is to print out the character we’ve received so that as we type,
we can see our input appear in the window. Before drawing text (or anything)
in a window, we need a handle to its device context. You may recall that, in
P <1> C:\QCWIN\BIN\KEY.C
/* QuickCase:W KNB Version 1.00
3
▲
^include "KEY.h"
/*************************************************#*******»**
/*
r* Windows 3.0 Main Program Body
[/* ♦
□J Id ■
L rr 00006 loot I
Chapter 1, we got a handle to the device context in the WM_PAINT case with
BeginPaint( ) and then released it when we were done with EndPaint( ) (this
code was generated for us by QuickCase:W):
case WM_CHAR:
hDC = GetDC(hWnd);
ReleaseDC(hWnd, hDC);
break;
Now we need to store the incoming characters, whose ASCII values will be in
wParam. To do that, we can set aside a character buffer which we might call
cBufferf ]. In addition, we’ll need to know the number of characters in that
buffer when we want to print it with TextOut( ), so we can also set up an
integer named nCharacters to store that. Note that we declare them static in
WndProc( ) so that their values are preserved between window messages —
that is, between successive keystrokes:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code
—> static int nCharacters 0;
—» static char cBuffer[10];
TEXTMETRIC
Keyboard and Mouse Input 85
case WM_CHAR:
hDC = GetDC(hWnd);
—> if (nCharacters < 10) {
—> cBuffer[nCharacters] = (char) wParam;
—> nCharacters++;
-> }
ReleaseDC(hWnd, hDC);
break;
Now we can display the string (in cBuffer [ ]) on the screen. To do that, we
only need to call TextOut( ), which requires the handle of the window we’re
printing to, hWnd, the location we want to print to — which we can simply
make (0, 0), the upper left corner of our Window, a long pointer to cBuffer [ ]
(i.e., (LPSTR) cBuffer, where (LPSTR) is Window’s long pointer to string type
override), and the number of characters to print. That looks like this:
case WM_CHAR:
hDC = GetDC(hWnd);
if (nCharacters < 10){
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
}
—> TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
ReleaseDC(hWnd, hDC);
break;
TIP When printing text, the coordinates you give — (0, 0) in our example here
— specify the top left pixel of the text string you’re printing.
And that’s it; now our switch statement in WndProc( ) looks like this:
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
—» case WM_CHAR:
hDC = GetDC(hWnd);
if (nCharacters < 10){
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
}
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
ReleaseDC(hWnd, hDC);
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
When we run the program in the development environment and type some¬
thing, those letters appear as in Figure 3-4. Congratulations: Now you’re han¬
dling keyboard input! The listing of key.c appears in Listing 3-1.
Note that we put the text at location (0, 0), but we could have chosen another
location. To do that, we should know something about the size of window
we’re working in. One way of determining that is with GetClientRect(hWnd,
IpRect). We pass a window handle and a long pointer to a RECT structure to
GetClientRect( ), and it fills the RECT structure with the dimensions of our
window’s client area. The RECT structure, which we’ve seen before, defines a
rectangle like this:
SM Constant Means
Note that we have not included any provision for backspace (‘\b’), tab (‘\t’), or
other control characters. In addition, the window is redrawn when it is resized,
so if you resize this window, the words will disappear (the way to fix that is to
draw the same string again in the WM_PAINT case).
One easy way to add tabs to our output is to use the TabbedTextOut( )
function instead of TextOut( ). This way, you specify the tab positions you
want at the same time you send the string to be printed. We’ll see how to
delete characters later.
/ **************************** ******************************************** /
/★ */
/ * Windows 3.0 Main Program Body */
★
/ */
Keyboard and Mouse Input 89
/* The following routine is the Windows Main Program. The Main Program */
/* is executed when a program is selected from the Windows Control */
/* Panel or File Manager. The WinMain routine registers and creates */
/* the program's main window and initializes global objects. The */
/* WinMain routine also includes the applications message dispatch */
/* loop. Every window message destined for the main window or any */
/* subordinate windows is obtained, possibly translated, and */
/* dispatched to a window or dialog processing function. The dispatch */
/* loop is exited when a WM_QUIT message is obtained. Before exiting */
/* the WinMain routine should destroy any objects created and free */
/* memory and other resources. */
/* */
/************************************************************************/
(continued)
90 Peter Norton’s QuickC for Windows
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString,
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
/* the respective message service routines, which are set apart by the */
/* CASE statements. The window procedures must provide an appropriate */
/* service routine for its end user initiated messages, as well as the */
/* general Windows messages (i.e., WM_CLOSE message). If a message is */
/* sent to this procedure for which there is no programmed CASE clause */
/* (i.e., no service routine), the message is defaulted to the */
/* DefWindowProc function, where it is handled by Windows */
/* */
/* For the end-user initiated messages, this procedure is concerned */
/* principally with the WM_COMMAND message. The menu control ID (or the */
/* corresponding accelerator ID) is communicated to this procedure in */
/* the first message parameter (wParam). The window procedure provides */
/* a major CASE statement for the WM_COMMAND message and a subordinate */
/* SWITCH statement to provide CASE clauses for the message service */
/* routines for the various menu items, identified by their ID values. */
/* */
/* The message service routines for the individual menu items are the */
/* main work points in the program. These service routines contain the */
/* units of work performed when the end user select one of the menu */
/* controls. The required application response to a menu control is */
/* programmed in its associated CASE clause. The service routines may */
/* contain subroutine calls to separately compiled and libraried */
/* routines, in-line calls to subroutines to be embodied in this source */
/* code module, or program statements entered directly in the CASE */
/* clauses. Program control is switched to the appropriate service */
/* routine when Windows recognizes the end event and sends a WM_COMMAND */
/* message to the window procedure. The service routine provides the */
/* appropriate application-specific response to the end user initiated */
/* event, then breaks to return control to the WinMain() routine which */
/* continues to service the message queue of the window(s). */
/* */
/************************************************************************/
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG 1Param)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc = 0; /* return code */
static int nCharacters = 0;
static char cBuffer[10];
TEXTMETRIC tm;
switch (Message)
(continued)
92 Peter Norton’s QuickC for Windows
{
case WM CREATE:
/* The WM_CREATE message is sent once to a window when the */
/* window is created. The window procedure for the new window */
/* receives this message after the window is created, but */
/* before the window becomes visible. */
/* */
/* Parameters: • */
/* */
/* lParam - Points to a CREATESTRUCT structure with */
/* the following form: */
/* */
/* typedef struct */
/* { */
/* LPSTR lpCreateParams; */
/* HANDLE hlnst; */
/* HANDLE hMenu; */
/* HWND hwndParent; */
/* int cy; */
/* int cx; */
/* int y; */
/* int x; */
/* LONG style; */
/* LPSTR IpszName; */
/* LPSTR IpszClass; */
/* DWORD dwExStyle; */
/* } CREATESTRUCT; */
case WM_CHAR:
hDC = GetDC(hWnd);
if (nCharacters < 10){
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
}
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
ReleaseDC(hWnd, hDC);
break;
Keyboard and Mouse Input 93
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/ ************************************************************************ j
/* */
/ * nCwRegisterClasses Function */
/★ */
/ * The following function registers all the classes of all the windows */
/ * associated with this application. The function returns an error code */
/ * if unsuccessful, otherwise it returns 0. */
/* */
/ ************************************************************************/
int nCwRegisterClasses(void)
{
(continued)
94 Peter Norton’s QuickC for Windows
return(0) ;
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
mentioned in the beginning of the chapter, that location is called the inser¬
tion point or caret. It turns out that the caret is controlled by five Windows
functions:
CreateCaret( )
SetCaretPos( )
ShowCaret( )
HideCaret( )
DestroyCaret( )
Let’s put all of them to work. We might generate a new program using
QuickCase:W which we can call key2. We can then create a caret in the
WM_CREATE case of key2’s WndProc( ) procedure, which currently looks
like this (and which is executed when the window is first created):
case WM_CREATE:
We’re going to have to specify the height and width of the caret we want, and
we should tie that to the current size of the font used in our window. The
current font is stored in the window’s device context. In fact, as we’ll see later,
a device context holds information about not only the current font but many
other drawing options as well, such as the current drawing color, the back¬
ground color, what font we’re using, and so on. All these options stay set in a
window’s device context until we change them.
To get the size of the current font used in our window’s device context, we use
the Windows function GetTextMetrics( ) like this:
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
Note that we had to have a handle to the device context (which we got from
using GetDC) to use GetTextMetrics ( ). GetTextMetrics( ) fills a TEXTMET-
RIC structure that gives all the characterstics of the current font, like this:
96 Peter Norton ’s QuickC for Windows
The two we’re interested in here are tmHeight, the height of the current font,
and tmAveCharWidth, the average width of the font’s characters. We can store
those like this (where the cx or cy prefix means count in the x or y direction):
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
—> cxChar = tm.tmAveCharWidth;
—> cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
Now we’re ready to create our caret, which we do with CreateCaret( ). While
it should be as tall as the current font’s characters, it should not be as wide;
let’s give it one quarter the width of a character. We do that by passing to
CreateCaret( ) the handle of the window we want the caret to appear in, a
NULL value indicating that we want to use the default caret (a vertical bar),
and the x and y dimensions of the caret:
Keyboard and Mouse Input 97
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
—> CreateCaret(hWnd, NULL, cxChar/4, cyChar);
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
Now that the caret is created, we can display it in our window. First, we posi¬
tion the caret to our original typing position, (0, 0), with SetCaretPos( ), and
then display it with ShowCaret( ):
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
—> SetCaretPos(0, 0);
—> ShowCaret(hWnd) ;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
And that’s it for getting a caret in our window. Now, however, we have to
update its position as we type (note that we don’t have to do this in DOS). We
can borrow the WM_CHAR case from our earlier program key.c, which allows
us to display characters as we type them:
case WM_CHAR:
hDC = GetDC(hWnd);
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
ReleaseDC(hWnd, hDC);
break;
Our first operation should be to hide the caret while we update the string on
the screen:
case WM_CHAR:
hDC = GetDC(hWnd);
—> HideCaret(hWnd);
cBuffer[nCharacters] = (char) wParam;
98 Peter Norton s QuickC for Windows
nCharacters++ ;
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
ReleaseDC(hWnd, hDC) ;
break;
Now we have to set the caret’s new position (to the end of the string on the
screen) before displaying it. We do that with SetCaretPos( ), which takes a
(x, y) location for the caret.
»
It sounds easy enough, but the trouble is that we have to know how long the
string we’ve just printed is — and since the default Windows font allows each
character to vary in width, that could be a problem. The solution is to use a
new Windows function, GetTextExtent( ). Since the font is a characterstic of
the device context, we have to pass hDC to GetTextExtent( ). In addition, we
have to pass a pointer to the string whose length (in pixels) we wish to know,
and the length of the string. GetTextExtent( ) returns a double, which we can
cast to an int for SetCaretPos( ). Then we can show the caret in its new
position at the end of the just printed string like this:
case WM_CHAR:
hDC = GetDC(hWnd);
HideCaret(hWnd);
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
—> SetCaretPos((int) GetTextExtent(hDC, (LPSTR) cBuffer, nCharacters), 0);
—> ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
The result is shown in Figure 3-5; now we have a blinking caret that indicates
the location where new text will go. In addition, however, we should destroy
the caret when we’re done with our application and ready to leave. That can
be done in the WM_CLOSE case with DestroyWindow( ), in which case we
exit:
case WM_CHAR:
hDC = GetDC(hWnd);
HideCaret(hWnd);
cBuffer[nCharacters] = (char) wParam;
Keyboard and Mouse Input 99
nCharacters++;
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
SetCaretPos((int) GetTextExtent(hDC, (LPSTR) cBuffer, nCharacters), 0) ;
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC) ;
break;
DestroyCaret();
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
The final program, key2.c, appears in Listing 3-2. The other files — the .def,
.mak, .rc, and .h files — are the same as for our previous application named
key, and they’re generated automatically by QuickCase:W.
In general, it’s more appropriate to show the cursor when your application
gets the input focus, and to hide it again when you lose it. You can tell when
you’re getting the input focus by adding a WM SETFOCUS case, and when
you’re losing it in a WM_KILLFOCUS case.
/************************************************************************/
%
/***********************************************************************/
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
TEXTMETRIC tm;
static int nCharacters = 0;
static char cBuffer[10];
int cxChar, cyChar, nRc=0;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
(continued)
102 Peter Norton’s QuickC for Windows
case WM_CHAR:
hDC = GetDC(hWnd);
HideCaret(hWnd);
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
SetCaretPos((int) GetTextExtent(hDC, (LPSTR) cBuffer, \
nCharacters), 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
DestroyCaret();
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/************************************************************************/
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
(continued)
104 Peter Nortons QuickC for Windows
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
This is alright as far as it goes, but there are still many faults to key2.c. For
example, we have no choice but to display our character string starting at (0,
0). We can fix that by using, say, the mouse to indicate where we want the caret
to appear in our window. After moving the caret, we’re free to continue
typing, and we should see our words appear at the new location. Let’s look
into this next — bringing us to the topic of interpreting mouse movements.
The most important messages for our use will involve the right or left buttons:
WM_LBUTTONDOWN, WM_LBUTTONUP, WMRBUTTONDOWN, and
WM_RBUTTONUP. Using these messages, and decoding the data sent to us
in wParam and IParam, we’ll be able to determine where the mouse cursor is
when some action was taken with the buttons. For these messages, IParam
contains the window position of the mouse — at the time that the mouse
message was generated (i.e., not necessarily the current position of the
mouse).
In particular, the high word of IParam contains the y location of the mouse
cursor, and the low word contains the x location. The usual procedure is to
extract these coordinates using the LOWORD( ) and HIWORD( ) Windows
macros, as we’ll do. Another handy way of decoding the information in
IParam is to use the Windows MAKEPOINT ( ) macro to fill a structure of type
POINT:
my point = MAKEPOINT(IParam);
as the mouse moves, it sends only so many messages a second, as we’ll see
when we construct our paint application.
It’s also important to know that if the user moves the mouse out of our
window, we may get a WM LBUTTONDOWN message without ever getting
a WM_LBUTTONUP message (or the reverse), so beware of programming
these in pairs.
The above mouse messages all refer to the client area of our window; that is,
the area under our control. But since all messages for our window pass
through WndProc( ), that includes even the nonclient area messages (i.e., for
the menu bar, the system menu if there is one, and so on). Using these
messages, Windows knows when to move, resize, or close our window. Al¬
though Windows applications very rarely use such messages, here they are for
reference (note that NC stands for nonclient area):
Note that since these messages refer to nonclient areas, they can’t use client
area coordinates (unless they use negative values). Instead, they use screen area
coordinates like this (starting in the upper left corner of the screen):
(0,0) x
1 (0,0) x--
y
Next, we might move the mouse cursor somewhere else and click it. The caret
should then move to that location:
To do this, let’s use left button clicks — which means that we should intercept
left button down messages in our program key2.c:
case WM_LBUTTONDOWN:
break;
108 Peter Norton ’s QuickC for Windows
We might want to change the title bar of key2.c, which now reads Key Input
(see Figure 3-5) to, say, Mouse Input. We can do that in QuickCase:W. Just
start QuickCaseiW, and open key2.win with the File menu’s Open... item.
Next, click the title bar of the window we’re designing — which now reads Key
Input as in Figure 3-6 — and change it, using the Title dialog box, to Mouse
Input as in Figure 3-7.
QuickCase:V■/ - (KEY2.WIN)* E
UI le Design Build Tools Options Help
Generate jt
F . 33
..«
Small changes like this do not bother QuickCase:W; we can just select the
Update item — as in Figure 3-8 — in its Build menu to update key2.c to in¬
clude the new title. And that’s it; QuickCase:W generates the new version of
key2.c, and we’re ready to start coding for mouse input.
We can begin by decoding the x and y coordinates of the new caret position
— which we can call our new (x, y) text origin — by decoding IParam like this
(recall that the low word holds the x coordinate and the high word the y
coordinate):
case WM_LBUTTONDOWN:
xOrigin = LOWORD(IParam);
yOrigin = HIWORD(IParam);
break;
Also note that we have to declare xOrigin and yOrigin static in WndProc( ):
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG IParam)
TEXTMETRIC tm;
—> static int nCharacters = 0, xOrigin = 0, yOrigin = 0;
static char cBuffer[10];
Note that we also initialize xOrigin and yOrigin to 0, although that is done
anyway since they are static. The point (xOrigin, yOrigin) will hold the new
text origin, which means that we have to start by placing the caret there. We
do that by first hiding the caret (if we moved the caret abruptly, while it
happened to be on the screen, it would leave its image behind):
case WM_LBUTTONDOWN:
xOrigin = LOWORD(lParam);
yOrigin = HIWORD(lParam);
—> HideCaret(hWnd);
break;
Next, we set the caret position to (xOrigin, yOrigin) — the location the
mouse was clicked at — and show it again, like this:
case WM_LBUTTONDOWN:
xOrigin = LOWORD(lParam);
yOrigin = HIWORD(lParam);
HideCaret(hWnd);
—> SetCaretPos(xOrigin, yOrigin);
—> ShowCaret(hWnd);
break;
Also, we have to zero the character string as well so that we don’t print the
current string out at the new screen location. We kept track of the string’s
length in the static int nCharacters, like this in the WM_CHAR case:
case WM_CHAR:
hDC = GetDC(hWnd);
HideCaret(hWnd);
—> cBuffer[nCharacters] = (char) wParam;
—> nCharacters + +;
TextOut(hDC, 0, 0, (LPSTR) cBuffer, nCharacters);
SetCaretPos((int)GetTextExtent(hDC, \
(LPSTR) cBuffer, nCharacters), 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
Keyboard and Mouse Input 111
And that means that we only have to set nCharacters to 0 to zero the string and
start over:
case WM_LBUTTONDOWN:
xOrigin = LOWORD(lParam);
yOrigin = HIWORD(lParam);
HideCaret(hWnd);
SetCaretPos(xOrigin, yOrigin);
ShowCaret(hWnd);
—> nCharacters = 0;
break;
Now we’ve moved the caret to a new location in our window and zeroed the
character string. The next step is to make sure that when we print, the charac¬
ters appear at the right position. That is, our TextOut( ) statement needs to be
updated to include xOrigin and yOrigin. In addition, when we advance the
caret, we have to make sure that it is with respect to (xOrigin, yOrigin), and
not (0, 0):
case WM_CHAR:
hDC = GetDC(hWnd);
HideCaret(hWnd);
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
—> TextOut(hDC, xOrigin, yOrigin, (LPSTR) cBuffer, nCharacters);
—> SetCaretPos(xOrigin + (int) GetTextExtent(hDC, \
—> Caret(LPSTR) cBuffer, nCharacters), yOrigin);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
And that’s it; the full listing appears in Listing 3-3. The results of this program
appear in Figure 3-9; as you can see, now we can move the caret around in our
window simply by using the mouse.
That’s it for this chapter, then: We’ve gotten a good introduction to both
keyboard and mouse input. Next, let’s put them to use in the following chap¬
ter, where we start working with menus and adding even more power to our
programs.
112 Peter Norton’s QuickC for Windows
strcpy(szAppName, "KEY2");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
Keyboard and Mouse Input 113
return nRc;
}
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
(continued)
114 Peter Norton’s QuickC for Windows
{
HMENU hMenu=0; /* handle for the menu */
TEXTMETRIC tm;
static int nCharacters = 0, xOrigin = 0, yOrigin = 0;
static char cBuffer[10];
int cxChar, cyChar, nRc 0;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
case WM_CHAR:
hDC = GetDC(hWnd);
HideCaret(hWnd);
cBuffer[nCharacters] = (char) wParam;
nCharacters++;
TextOut(hDC, xOrigin, yOrigin, (LPSTR) cBuffer, \
nCharacters);
SetCaretPos(xOriginr(int)GetTextExtent(hDC, \
(LPSTR) cBuffer, nCharacters), yOrigin);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
case WM_LBUTTONDOWN:
xOrigin = LOWORD(lParam);
yOrigin = HIWORD(lParam);
hDC = GetDC(hWnd);
HideCaret(hWnd);
nCharacters = 0;
SetCaretPos(xOrigin, yOrigin);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
default
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc
/************************************************************************/
/* */
/* nCwRegisterClasses Function */
/* . */
/* The following function registers all the classes of all the windows */
(continued)
116 Peter Norton’s QuickC for Windows
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS).);
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Menus
We’ re all familiar with menus under Windows; menus are the (extraordinar¬
ily popular) way of letting users select from among a variety of program op¬
tions. We can add menus to our programs easily with QuickCase:W, and we’ll
investigate exactly how that works in this chapter. Before, in Windows, adding
menus was quite a programming chore — now it’s as easy as using a dialog
box in QuickCase:W.
We’ll see all the techniques necessary to handle menus in this chapter: how to
interface a menu to our program in the first place; how to add accelerator
keys; how to gray items out; or how to add check marks to menu items. We’ll
even see how to add menu items at run-time when we write a phone book
program that will let us add people’s names to our File menu at run-time. With
all this coming up, then, let’s get started at once, seeing how to work with
menus in our programs.
Menu Conventions
The type of menu we’ll be adding to our programs are called pop-up menus
— also called dropdown menus or submenus — and they appear on com¬
mand. At the top of the window is the title bar, and directly underneath that
is the program’s menu bar — also called the main menu or top-level menu:
117
118 Peter Norton’s QuickC for Windows
Here, three of the most common menus are indicated —- File, Edit, and Help
(note that Help is flush right). When the user selects one of these, like the File
menu (using either the mouse or keyboard), the corresponding popup menu
appears:
(Title Bar)
Open . . .
Close
Exit
It’s standard for Windows applications to have a File menu (often even if they
don’t handle files) because the user has come to expect it; the final menu item
in the File menu is almost always the Exit item, allowing the user to quit the
program. You might note that the Open... item has ellipses — three dots —
after it, indicating to the user that selecting this item opens a dialog box.
Other standard ways of working with menu items include graying out menu
items that are inappropriate and adding check marks in front of items to
indicate that a certain option is (and will remain) selected. We’ll adhere to
these user expectations in our programs as well.
QuickCase:W - (Untitled]
File Design Build loots Options Help
«>>
Your Window's Title Goes Here
Your Window's Menu Goes Here
33
menu 1.win, which will make QuickCase:W generate the files menul.mak,
menul.c, menul.rc, menul.h, and menul.def. Do that by selecting the Save
As... item in the File menu, and by saving menul.win in the associated dialog
box. Now we’re ready to start designing our program.
First, click the double angle brackets you see in the extreme left of our
window’s menu bar; this allows us to select the name of our first menu, the File
menu (although we’re not going to do any file handling in this chapter). A
dialog box opens — the Insert Menu Item dialog box — as shown in Figure
4-2.
You can see the different types of menu items that we can add in Figure 4-2 —
strings (like “File”), separators (the horizontal bars used to separate groups of
similar menu items), or bitmaps (which allow us to design the appearance of
our menu items). We can also set the menu’s initial state here: Active or
Grayed. Since we want the File menu to be accessible, we’ll leave this setting as
Active. In addition, we have the option of specifying what happens when the
user opens this menu. We do that in the “Link to” box; currently, our menu
name is linked to a dropdown menu, which is exactly what we want.
The other two options (which we can select by clicking the down arrow in the
Link to list box) are Dialog Box (i.e., when the user clicks the menu, a dialog
box appears), or User-Defined Code (i.e., we can link to some of our own code
120 Peter Norton’s QuickC for Windows
Aecei editor:
"Initial State'
’T*) Active
O Grayed
Link to
Dropdown Menu 21
.link... |
OK | Cancel ] Help ]
so that when the user clicks a menu name, that code is run). At this point,
we’re linked to a dropdown menu, which is what we want, so click the OK
button and close the Insert Menu Item dialog box.
When you do, a dropdown menu template appears below the menu name
we’ve just added — File — as shown in Figure 4-3. Note the double angle
brackets here too, which means that we can click them to open a new dialog
box. Do so now, opening the Insert Menu Item dialog box again, as shown in
Figure 4-4. Now we’re ready to add a menu item, not a new menu, so there is
another Initial State option, Checked, which allows us to display the new
menu item as initially checked.
You can see another change in the “Link to” box as well: now the default state
is not to link to anything at all. It turns out that instead of linking to dialog
boxes or user-defined code, menu items will communicate to us through
messages, as we’ll see in a moment. For now, let’s add a menu item; we can
make our program display the “Hello, world.” message when a menu item —
called, say, Hello — is selected by the user. To do that, give this menu item the
Menus 121
■= QuickCase:W- (Untitled)* E0
File Edit Design Build lools Options Help
r Menu Input 3
F «»
«»
File
Style
led)’
Options Help
33
® String
Fil
O
O
Separator
Bjtmap...
aa
Name:
Accelerator:
Initial State
® Active O Checked
O Gjayed
Link to
None
a
ZonlkjUiv Unk... |
0* 1 Cancel 1 Help |
name Hello in the Name text box of the Insert Menu Item dialog box, and
then choose OK.
All that remains now is to generate the files required for our project; as before,
do that by selecting the Generate item in QuickCase:W’s Build menu. After
QuickCase:W indicates that the generation of menul.win is complete, exit
and start the QuickC for Window’s integrated development environment.
Load the menul project (menul.mak) from the Project menu, and load the
_ *
corresponding C code (menul.c) from the File menu. Let’s take a look at the
code that was generated for us — in particular, let’s look at the switch state¬
ment in the window function, WndProc( ), which is where we’ll handle the
messages sent from our menu items. That switch statement looks like this
now:
switch (Message)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
break; /* End of WM_CREATE */
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
As you can see, a new case has been added for us — WM_COMMAND. This is
a very important message, and it’s sent when the user has selected an option
that we’ve defined ourselves, like a menu item. Note that inside the
WM_COMMAND case itself, there is another switch statement, which exam¬
ines the value in wParam:
case WM_COMy[AND:
switch (wParam)
{
case IDM_F_HELLO:
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
Note that if the WM_COMMAND message was not a IDM F HELLO message,
we go to the default case in our internal switch statement, and pass the mes¬
sage on to the default Windows procedure, DefWindowProc ( ):
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
break;
default:
Now let’s add the code to print "Hello, world." like this, using TextOut( ):
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
—> hDC = GetDC(hWnd) ;
—> TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
—> strlen("Hello, world."));
—> ReleaseDC(hWnd, hDC);
break;
default:
return DefWindowProc(hWnd, Message, wParam, IParam);
}
break; /* End of WM_COMMAND */
And that’s it; that’s all we need. Run menul by selecting the Go menu item in
QuickC for Windows’ Run menu, and by clicking the Hello menu item in the
File menu. When you do, “Hello, world.’’ appears in the window, as in Figure
4-5. That completes our first menu program — it was that quick. The C code
appears in Listing 4-1.
Menus 125
/************************************************************************ /
/* / *
/************************************************************************ /
strcpy(szAppName, "MENUl");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, \
sizeof (szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Menu Input", /* Window's title */
W S_CA PTION ! /* Title and Min/Max */
WS SYSMENU ! /* Add system menu box */
(continued)
126 Peter Norton’s QuickC for Windows
WS_MINIMIZEBOX 1
1 /* Add minimize box */
WS_MAXIMIZEBOX 1 Add maximize box
1 /* */
WS_THICKFRAME 1
1 /* thick sizeable frame */
WS_CLIPCHILDREN ! /* don't draw in child windows */
WS_OVERLAPPED,
CW_USEDEFAULT, 0, /* Use default X, Y */
CW_USEDEFAULT, 0, /* Use default X, Y ★ //
NULL, /* Parent window's handle */
NULL, /* Default to Class Menu */
hlnst, /* Instance of window */
NULL); /* Create struct for WM_CREATE */
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG 1Param)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
switch (Message)
Menus 127
{
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.",\
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
ReleaseDC(hWnd, hDC);
break;
default:
return DefWindowProc(hWnd, Message, wParam, \
1Param);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
break; /* End of WM_CREATE */
(continued)
128 Peter Norton’s QuickC for Windows
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
return OL;
} /* End of WndProc */
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Menus 129
#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2
It’s also assigned the arbitrary (but unique) values of 1,000 and 1,050 for these
constants. The menu itself is designed in menul.rc, the resource file, like this
(note that menul.rc also includes menul.h so it has access to the definition of
IDM_F_HELLO):
#include "MENUl.h"
END
130 Peter Norton’s QuickC for Windows
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class
END
This is a resource script, indicating that we want a pop-up menu named File, with
one item in it named Hello. In addition, when Hello is selected, we want to
receive IDM F HELLO in a WM COMMAND message (i.e., in wParam). This
MENU structure has the name MENU1, which is the same as our application
name (that is, QuickCase:W has given it the same name), and this is how we
load it into our window class in nCwRegisterClasses( ):
If you want a menu name to be flush right in the menu bar, such as the
Help menu, place the characters “\a” before the menu’s name in the
resource file, like this: POPUP NaHelp”.
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
Now our menu bar will have a File menu in it, and the File menu will have one
entry — Hello. It’s usual for a File menu to have at least two items in it,
Menus 131
however, and the second of them is an Exit item. Since that’s so popular, let’s
add it to our application now.
Start by loading menul.win back into QuickCase:W with the Open... item in
its File menu. Then open menul’s File menu as shown in Figure 4-6. We can
add an Exit item simply by clicking the double angle brackets in this menu
(which now appear under the Hello item) and by typing Exit in the Insert
Menu Item dialog box that appears (which will make sure that the Exit item
follows the Hello item in our menu). Now our menu looks like Figure 4-7, and
we’re ready to update our code.
It’s important to note that we can edit previous menu items in our menus —
such as the Hello item — using QuickCase:W. You can click pre-existing
items, which brings up the Update Menu Item dialog box (which is just like
the Insert Menu Item dialog box), or you can highlight them (using the up
and down arrows on the keyboard if necessary) and then use QuickCase:W’s
Edit menu, as shown in Figure 4-8. For example, the Cut item in the Edit
menu cuts the currently highlighted menu item, the Insert item allows you to
insert a new menu item above the currently highlighted item, and so on.
File Edit
QuickCaselW - (Untitled)*
Design Build Tools Options Help
m
F9 Menu Input 33
F «» _ 1
Hello
ifi
QuickCase:W- (Untitled)1
File Edit Design Build Tools Options Help
PI «»
I Hello"
Exit
«»
TIP Note that we can also add a separator bar above the Exit menu item in
QuickCase:W by clicking the Separator menu item button, and that this is
often a good idea to keep Exit visually separate from the rest of the items in
the File menu.
QuickCase:W - (Untitled)1
File Design Build Tools Options Help
File Paste
Shift+Del
Shift+lns
u Input 30
Delete Del
Insert... Ctrl+I
Update... Ctrl+U
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
ReleaseDC(hWnd, hDC) ;
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
Making this item active is easy; all we need to do is to copy the code from the
WM_CLOSE case that appears underneath it, like this:
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
134 Peter Norton’s QuickC for Windows
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
ReleaseDC(hWnd, hDC);
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
—> DestroyWindow(hWnd) ;
Now our application has two active entries in the File menu: Hello and Exit, as
shown in Figure 4-9. Both of these menu items work — we’ve come far in our
To do that, bring up menul in QuickCase:W and click the Hello item in our
File menu, bringing up the Update Menu Item dialog box. Add an amper¬
sand, 8c, in front of the H in Hello (i.e., in the Name text box), and type
CTRL+H in the Accelerator text box, as shown in Figure 4-10. This makes H
into the ALT key for this menu item, and makes CTRL+H into its accelerator
key. Also, add an ampersand in front of the x in Exit (i.e., E&xit). Now, to see
the result of this in action, select the Update item in QuickCase:W’s Build
menu and rebuild the menul project. When we take a look in the menul.rc
file, we see that the keys we’ve selected are indicated like this:
MENUl MENU
BEGIN
POPUP "File"
BEGIN
—> MENUITEM "&Hello\tCTRL+H", IDM_F_HELLO
—» MENUITEM "E&xit", IDM_F_EXIT
END
END
CTRL+H
Update Menu Item
File t
Style-
Suing
IN)
ons Help
31
File
O
Q
Separator
Bitmap...
an
Name: tcHello
Accelerator: CTRL+H
”Initial State
® Active O Checked
O Grayed
Link to
None
HI
iivt link..
I OK 1
1 Cancel |
i Help |
To sum the process up, then, when we want to indicate a menu item’s ALT
key, we only need to just place an ampersand in front of it in the Name dialog
box like this: &Hello or E&xit. On the other hand, we have to type the accel¬
erator explictly into the Accelerator box. The valid accelerators in
QuickCase:W are:
F1-F12
CTRL+F1-F12
ALT+F1-F12
SHIFT+F1-F12
CTRL+A-Z
SHIFT+A-Z
ALT+/SHIFT+/CTRL+TAB
ALT+/ SHIFT+/CTRL+ARROW KEYS
ALT+/SHIFT+/CTRL+HOME
ALT+/SHIFT+/CTRL+END
Menus 137
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
ReleaseDC (hWnd, liDC) ;
break;
We can gray out the Hello item with the Windows Enableltem( ) function, like
this:
138 Peter Norton’s QuickC for Windows
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.",\
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
—> hMenu = GetMenu(hWnd);
—» EnableMenuItem(hMenu, IDM_F_HELLO, MF_GRAYED);
ReleaseDC(hWnd, hDC);
break;
Note that we first get a handle to our menu system with GetMenu( ). This is a
necessary first step, and it returns a handle of type HMENU to our entire
menu system (i.e., all the menus in our window; this menu system is called
MENU1 above). We use that handle in Enableltem( ), passing the menu ID of
the item we want to gray out (IDM_F_HELLO) and specifying that we want it
gray with the MF GRAYED constant (MF stands for Menu Function). The
other option here is MFENABFED, which enables the menu item. With this
new code, Hello is grayed out after we display our message, as shown in Figure
4-12.
That was easy enough; now let’s add a menu item that we can use with check
marks. One way that check marks are frequently used in Windows is with a
font menu, indicating which font is currently active. That is, a list of fonts
appears, and when the user selects one a check mark appears in front of it,
making it the active font. Fet’s add a Font menu to menul, allowing us to print
out our message in more than just the Windows default font.
It turns out that there are a few other fonts readily available in Windows, and
one of them is the System Fixed Font, where every character has a fixed width
equal to that of every other character (much like the standard DOS font). The
way to switch the current device context to that font is like this:
Let’s put this to work by adding a new Font menu to menul. Open menul .win
in QuickCase:W and click the double angle brackets in our window’s menu
bar (not at the bottom of the File menu, which would only add a new item to
that menu). When the Insert Menu Item dialog box appears, add a new menu
named Font. Next, add a menu item in that menu named System Fixed Font,
as shown in Figure 4-13. Finally, choose the Update item in the Build menu,
and open menul in the QuickC environment. We see that a new case —
IDM_F_SYSTEMFIXEDFONT — has been added to the WM COMMAND
switch statement like this:
140 Peter Norton’s QuickC for Windows
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
hMenu = GetMenu(hWnd);
EnableMenuItem(hMenu, IDM_F_HELLO, MF_GRAYED);
ReleaseDC(hWnd, hDC);
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
—» case IDM_F_SYSTEMFIXEDFONT:
/* Place User Code to respond to the */
/* Menu Item Named "System Fixed Font" here. */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
When the user clicks the System Fixed Font item in our font menu, we can
erase the currently displayed message (“Hello, world.”) in preparation for
printing it out again in the new font.
The way to erase a string is simply to change the text color — using
SetTextColor( ) — to the current background color, which we can find with
GetBkColor( ). Windows stores colors in variables of type COLORREF, as
we’ll see in the next chapter on graphics. GetBkColor( ) returns a variable of
that type, corresponding the current background color. After we pass that
value to SetTextColor( ), we can print the string “Hello, world.” again with
TextOut( ) to erase the version of that string that now appears in the default
font. Note also that the current text color is returned by SetTextColor( ) and
that we restore it before continuing (crOldTextColor is a variable of type
COLORREF):
Menus 141
case IDM_F_SYSTEMFIXEDFONT:
/* Place User Code to respond to the */
/* Menu Item Named "System Fixed Font" here. */
// Erase current string!
—> hDC = GetDC(hWnd);
ReleaseDC(hWnd, hDC);
break;
Now the window is blank again. Next, we change the font in our device
context with SelectObject( ) and GetStockObject( ). Then we can print the
message out in the new font like this:
case IDM_F_SYSTEMFIXEDFONT:
/* Place User Code to respond to the */
/* Menu Item Named "System Fixed Font" here. */
hDC = GetDC(hWnd);
crOldTextColor = SetTextColor(hDC,GetBkColor(hDC));
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));
SetTextColor(hDC, crOldTextColor);
—> SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT)) ;
—> TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));
ReleaseDC(hWnd, hDC);
break;
Now we can place a checkmark in front of the System Fixed Font item in our
Font menu using the Windows function CheckMenuItem( ). This function
operates much like EnableMenuItem( ), which we saw before: We have to get
a handle to our menu system (with GetMenu( )), indicate which item we want
to work on (IDM_F_SYSTEMFIXEDFONT), and tell it to check it
(MF CHECKED; the other option is MF_UNCHECKED):
case IDM_F_SYSTEMFIXEDFONT:
/* Place User Code to respond to the */
/* Menu Item Named "System Fixed Font" here. */
hDC = GetDC(hWnd);
crOldTextColor = SetTextColor(hDC,GetBkColor(hDC));
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));
SetTextColor(hDC, crOldTextColor);
142 Peter Norton’s QuickC for Windows
That’s it; when the user selects System Fixed Font in our Font menu now, the
message reappears in system fixed font (which actually looks much like system
font, except that each character now has a constant width, and serifs have
been added). In addition, the System Fixed Font menu item appears checked,
as we can see in Figure 4-14. The C code, menul.c, appears in Listing 4-2, and
menul.rc appears in Listing 4-3.
Menu Input
Ti le Font |
iHello V System Fixed Font
Listing 4-2. Menul.c with Grayable Item and Checkable Font Menu.
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
(continued)
144 Peter Norton’s QuickC for Windows
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0 ; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds :PAINT information */
int nRc = 0; /* return code */
COLORREF crOldTextColor;
switch (Message)
{
case WM_COMMAND:
switch (wParam)
{
case IDM_F_HELLO:
hDC = GetDC(hWnd);
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
/* Place User Code to respond to the */
/* Menu Item Named "Hello" here. */
hMenu = GetMenu(hWnd);
EnableMenuItem(hMenu, IDM_F_HELLO, MF_GRAYED);
ReleaseDC(hWnd, hDC);
break;
Menus 145
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
case IDM_F_SYSTEMFIXEDFONT:
/* Place User Code to respond to the */
/* Menu Item Named "System Fixed Font" here. */
hDC = GetDC(hWnd);
crOldTextColor = SetTextColor(hDC,GetBkColor(hDC));
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
SetTextColor(hDC, crOldTextColor);
SelectObject(hDC, GetStockObject(SYSTEM_FIXED_FONT));
TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", \
strlen("Hello, world."));
hMenu = GetMenu(hWnd);
Chec kMenu11 em(hMenu, IDM_F_SYSTEMFIXEDFONT, MF_CHECKED) ;
ReleaseDC(hWnd, hDC);
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
break; /* End of WM_CREATE */
break;
(continued)
146 Peter Norton’s QuickC for Windows
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return OL;
} /* End of WndProc */
/
/* */
/* nCwRegisterClasses Function */
/* * /
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code * /
/* if unsuccessful, otherwise it returns 0. */
/* *
/
/************************************************************************ /
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************ j
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Listing4-3. Menul.rc.
#include "MENUl.h"
MENUl MENU
BEGIN
POPUP "File"
BEGIN
MENUlTEM "&Hello\tCTRL+H", IDM_F_HELLO
MENUlTEM "E&xit", IDM_F_EXIT
END
POPUP "Font"
BEGIN
MENUITEM "System Fixed Font", IDM_F_SYSTEMFIXEDFONT
END
END
(continued)
148 Peter Norton’s QuickC for Windows
MENUl ACCELERATORS
BEGIN
"AH", IDM_F_HELLO
END
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!" *
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END
And there’s still more that we can do with menus. For example, we can add
menu items to menus on the fly, as our program runs. Let’s look into that
immediately.
Then we could type a tab (the standard way to move to a new data field in
Windows), moving the caret to a new location, and then type the person’s
phone number:
Menus 149
To add this name to our phone book data base, we might have an Add Name
menu item:
Phone Book
File
Add Name
298-5995
When selected, this item adds the person’s name to the File menu and clears
the text in the window:
Phone Book
File
Add Name
Albert
We could then repeat the process, adding more names to the File menu like
this:
Phone Book
File
Add Name
Albert
George
Sam
If we then chose a name to look up from the menu, the corresponding name
and number should appear on the screen:
150 Peter Norton’s QuickC for Windows
Let’s put this into action. We start by designing our phone project in
QuickCase:W. Put in a File menu with a single menu item called Add Name,
as shown in Figure 4-15. Give it a title bar of Phone Book, and select the
Generate option in the Build menu to create the necessary files.
Now bring up phone.c in the QuickC environment. As usual, all the standard
cases appear in WndProc( )’s switch statement, along with a new one for the
Add Name menu item called IDM F ADDNAME. In our program, we’ll need
a handle to the actual pop-up menu we want to work with (File) so we can add
menu items to it. We can get that handle with the GetSubMenu( ) function
(i.e., GetMenu( ) returns a handle to the whole menu system and
GetSubMenu( ) returns a handle to one menu in that system, such as the File
menu).
We start the code by getting a handle to the whole menu system for our
window, hMenu, and then we get a handle to the File menu with
GetSubMenu( ) —we’ll need this handle later when we add items to the File
menu. The first parameter to GetSubMenu( ) is hMenu, and the second pa¬
rameter indicates the location of the pop-up menu we’re interested in, start¬
ing at 0. Since we want the first pop-up menu whose name appears in the
menu bar (i.e., File), we put a 0 there. This whole action only needs to be done
Menus 151
case WM_CREATE:
hMenu = GetMenu(hWnd) ;
hFileMenu = GetSubMenu(hMenu, 0);
break;
Note that since we’ll need hFileMenu later, we should declare it static:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
static HMENU hFileMenu; <—
We can also set up a screen caret in the WM_CREATE case, as we have done
before, and place it at (0, 0):
case WM_CREATE:
hMenu = GetMenu(hWnd) ;
hFileMenu = GetSubMenu(hMenu, 0);
—> hDC = GetDC(hWnd);
: GetTextMetrics(hDC, &tm) ;
: cxChar = tm.tmAveCharWidth;
: cyChar = tm.tmHeight;
: CreateCaret(hWnd, NULL, cxChar/4, cyChar);
: SetCaretPos(0, 0);
: ShowCaret(hWnd);
—> ReleaseDC(hWnd, hDC);
break;
Now we’re set to start reading keyboard input. The only control character we
have is the tab key; if the user presses the tab, they’re through entering the
person’s name and now want to enter the person’s phone number. To do that,
we want to move the caret down some distance; since we already know how
high characters are in the current font — i.e., we just stored that in cyChar
above — we can move the cursor down two lines (2 * cyChar) like this:
case WM_CHAR:
if ((char) wParam == '\t'){
bEnteringNumber =' TRUE;
hDC = GetDC(hWnd);
HideCaret(hWnd);
152 Peter Norton’s QuickC for Windows
We’ll be storing the names and numbers as character strings in our program.
To keep the two separate, we can introduce a Boolean variable named
bEnteringNumber to indicate whether the user is now entering a name or a
phone number (note that we set it TRUE above when they press a tab and
move down to enter a number). When it’s TRUE, they’re entering the
person’s phone number, so the characters we read should be stored in a
phone number string, which we might call asNumberString[ ] [ ] (the prefix
“as” means array of strings):
Phone Book
File
Albert
asNumberString[ ] [ ]
We can store the current number of names that have been stored in a variable
called nNames. That means that the string we want to add the just-typed
character to is asNumberString [nNames] [ ], and the current length of that
string is anNumberStrlen[nNames]. With this in mind, we can add this code
to store the just-typed character, increment anNumberStrlen [nNames], and
display the character:
case WM_CHAR:
if ((char) wParam == '\t'){
bEnteringNumber = TRUE;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 2 * cyChar);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
else {
hDC = GetDC(hWnd);
HideCaret(hWnd) ;
if(bEnteringNumber){ /* Number case */
asNameString[ ] [ ] asNumberString [ ] [ ]
That is, if the first person (person 0 in our arrays) we wanted to enter in our
phone book was “Albert,” and his phone number was “298-5995,” then as-
NameString[0] would be “Albert,” anNameStrlen[0] would be 6, as-
NumberString[0] would be “298-5995,” and anNumberStrlen[0] would be 8.
In code, it looks like this (note that we’re not going through all the details
because they’re less important than what’s coming up, which is adding the
names to our File menu):
case WM_CHAR:
if ((char) wParam == '\t'){
bEnteringNumber = TRUE;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 2 * cyChar);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
else {
hDC = GetDC(hWnd);
HideCaret(hWnd);
if(bEnteringNumber){ /* Number case */
asNumberString[nNames][anNumberStrlen[nNames]] = (char) wParam;
anNumberStrlen[nNames]+ +;
TextOut(hDC, 0, 2 * cyChar, (LPSTR) asNumberString[nNames],\
anNumberStrlen[nNames]);
SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
asNumberString[nNames], anNumberStrlen[nNames]), 2 * cyChar);
}
else{ /* Name case */
And that’s it for keyboard input. Now comes the important part. When the
user is finished typing in the person’s name and number, they select the Add
Name item in our File menu. In fact, the name and number are already in our
arrays, so we only have to add the person’s name to the File menu. We do that
with AppendMenu( ), which takes four parameters: a handle to the sub menu
Menus 155
we’re adding items in (hFileMenu), the type of addition we want to make (the
allowed types are: MF_STRING, MF_SEPARATOR, and MFJBITMAP), the ID
number we want to use for this new menu item, and the name of the item
itself. As far as the ID number for the new menu item goes, we can see that
QuickCase:W has used the ID number 1050 for the Add Name item in
phone.h:
#define IDS_ERR_REGISTER_CLASS 1
#define IDS ERR CREATE WINDOW 2
HWND hlnst;
HWND hWndMain;
This means that we can use, say, the IDs 1051-1060 for our (up to 10) new menu
items. The name of the item we want to add is simply asNameString[nNames]. In
code, we can define the new menu item and increment nNames in prepara¬
tion for the next one in the IDM F ADDNAME case like this:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
if(wParam == IDM_F_ADDNAME){
/* Place User Code to respond to the */
/* Menu Item Named "Add Name" here. */
—> AppendMenu(hFileMenu, MF_STRING, IDM_F_ADDNAME + 1 + nNames, \
(LPSTR) asNameString[nNames]);
—> DrawMenuBar(hWnd);
—> nNames++;
156 Peter Norton’s QuickC for Windows
Now we have to clear the window to prepare for the next name and number,
as well as reset the caret to location (0, 0). There is one easy way to blank our
window; when some part of our window is declared invalid, Windows draws
over it with the window’s background color and sends a WM_PAINT message
to us to repaint it. In fact, that’s what we want — to blank the window. We can
simply invalidate our entire window with the InvalidateRect( ) function, and
send ourselves a WM PAINT message like this:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
if(wParam == IDM_F_ADDNAME){
/* Place User Code to respond to the */
/* Menu Item Named "Add Name" here. */
AppendMenu(hFileMenu, MF_STRING, IDM_F_ADDNAME + 1 + nNames, \
(LPSTR) asNameString[nNames]);
DrawMenuBar(hWnd);
nNames++;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
—> InvalidateRect(hWnd, NULL, 1);
—> SendMessage(hWnd, WM_PAINT, 0, 0L) ;
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
}
the Add Name item is active. When the user selects it, the current name is
added to the File menu and the window is blanked so they can type more data
if they wish.
All we have left is the case where the user selects one of the names that they’ve
added to the File menu; in that case, we want to display the corresponding
data. We know which name they’re interested in because the menu ID value
(passed in wParam) will be between 1051 and 1060. That is, the name they
want is stored in asNameString [wParam - 1051] and the phone number is in
asNumberString [wParam -1051]. We want to blank the screen (which we can
do with InvalidateRect( ) again) and then simply display the requested name
and number like this:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
if(wParam == IDM_F_ADDNAME){
/* Place User Code to respond to the */
/* Menu Item Named "Add Name" here. */
AppendMenu(hFileMenu, MF_STRING, IDM_F_ADDNAME + 1 + nNames, \
(LPSTR) asNameString[nNames]);
DrawMenuBar(hWnd);
nNames++;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
}
else{
if(wParam >= IDM_F_ADDNAME + 1 && wParam <= IDM_F_ADDNAME + 10){
index = wParam - IDM_F_ADDNAME - 1;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L); // Blank window
—> TextOut(hDC, 0, 0, (LPSTR) asNameString[index], \
anNameStrlen[index]);
—> TextOut(hDC, 0, 2 * cyChar, (LPSTR) asNumberString[index],\
anNumberStrlen[index]);
158 Peter Norton’s QuickC for Windows
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
}
else
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
To retrieve a name, such as Albert, we can select it from the File menu. When
we do, the corresponding name and number are displayed again in our win¬
dow, as in Figure 4-18. The entire program, phone.c, appears in Listing 4-4,
and phone.rc appears in Listing 4-5.
*/
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
(continued)
160 Peter Norton’s QuickC for Windows
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
} /* End of WinMain */
/************************************************************************/
/* */
/* Main Window Procedure */
/* */
/* This procedure provides service routines for the Windows events */
/* (messages) that Windows sends to the window, as well as the user */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding */
/* keyboard accelerators. */
/* */
/************************************************************************/
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
static HMENU hMenu=0, hFileMenu;
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
TEXTMETRIC tm;
static int anNameStrlen[10 ] , anNumberStrlen [10];
static int cxChar, cyChar, nNames, index;
static BOOL bEnteringNumber;
static char asNameString[10] [20] , asNumberString[10] [2 0] ;
switch (Message)
{
case WM_COMMAND:
if(wParam == IDM_F_ADDNAME){
/* Place User Code to respond to the */
/* Menu Item Named "Add Name" here. */
AppendMenu(hFileMenu, MF_STRING, IDM_F_ADDNAME + 1 + \
nNames, (LPSTR) asNameString[nNames]);
DrawMenuBar(hWnd);
nName s + +;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
(continued)
162 Peter Norton’s QuickC for Windows
else {
if(wParam >= IDM_F_ADDNAME + 1 && \
wParam <= IDM_F_ADDNAME + 10){
index = wParam - IDM_F_ADDNAME - 1;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, “0, 0L);
TextOut(hDC, 0, 0, (LPSTR) asNameString[index], \
anNameStrlen[index]) ;
TextOut(hDC, 0, 2 * cyChar, \
(LPSTR) asNumberString[index],\
anNumberStrlen[index]);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
}
else
return DefWindowProc(hWnd, Message, wParam, \
1Param);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
hMenu = GetMenu(hWnd);
hFileMenu = GetSubMenu(hMenu, 0);
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
case WM_CHAR:
if ((char) wParam == '\t'){
bEnteringNumber = TRUE;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 2 * cyChar);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
Menus 163
else{
hDC = GetDC(hWnd);
HideCaret(hWnd);
(continued)
164 Peter Norton’s QuickC for Windows
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/************************************************************************/
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/***********************************************************************■*•/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset (Scwndclass , 0x00, sizeof (WNDCLASS) ) ;
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
PHONE MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Add Name", IDM_F_ADDNAME
END
END
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END
That concludes our survey of menus for the moment. Now let’s put what we’ve
learned to work in the next chapter when we design a mouse-driven paint
program.
V ' > \ • - “ '
'
*
Graphics and a
Mouse-driven Paint Program
Most of us are familiar with paint programs; using a paint program, you can
draw graphics images by selecting and using graphics tools. For example, you
can select a line drawing tool, press the left mouse button at the location
where you want to anchor one end of the line, move to the other end of the
line you want to draw, and release the mouse button. When you do, the
program draws the line for you. This is the kind of program we’re going to
develop in this chapter. With it, we’ll be able to create graphics images just like
any paint program, and, more importantly, we’ll get an idea of how to work
with graphics in QuickC for Windows.
Next, we have to design the Tools menu. There are a variety of graphics
functions available to us in Windows, so let’s place some of the most popular
in this menu: Point (draw a single point), Draw (freehand drawing), Line,
Rectangle, Ellipse, and Fill (fill a figure with solid color), as shown in
167
168 Peter Norton's QuickC for Windows
Figure 5-2. These are the graphics functions that we’ll work with in our pro¬
gram. In addition, give the first of our tools, Point, an initial state of Checked
in the Insert Menu Item dialog box so that when the user opens our Paint
program, the Point tool will already be selected.
File Edit
QuickCase:W - (PAINT.WIN)
Design Build Tools Options Help
33
After creating the menus as shown, select the Generate item in QuickCase:W’s
Build menu, and generate the files we’ll need for the paint application. When
they’re created, open paint.c in the QuickC for Windows development envi¬
ronment. There, our menu interface code (part of the WM_COMMAND case
in WndProc( )) looks like this:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_NEW:
/* Place User Code to respond to the */
/* Menu Item Named "New" here. */
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
break;
case IDM_T_POINT:
/* Place User Code to respond to the */
/* Menu Item Named "Point" here. */
break;
case IDM_T_DRAW:
/* Place User Code to respond to the */
/* Menu Item Named "Draw" here. */
break;
case IDM_T_LINE:
/* Place User Code to respond to the */
/* Menu Item Named "Line" here. */
break;
case IDM_T_RECTANGLE:
/* Place User Code to respond to the */
/* Menu Item Named "Rectangle" here. */
break;
case IDM_T_ELLIPSE:
/* Place User Code to respond to the */
/* Menu Item Named "Ellipse" here. */
break;
case IDM_T_FILL:
/* Place User Code to respond to the */
/* Menu Item Named "Fill" here. */
break;
170 Peter Norton’s QuickC for Windows
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
We can handle the New (that is, IDM_F_NEW) and Exit (IDMFEXIT) cases
easily —just as we did in the last chapter, we can clear our window by invali¬
dating the entire client area and sending a WMPAINT message. In that case,
Windows will blank the window for us, which is exactly what we want. Our
program will not place any extra code in the WM_PAINT case, so a
WM_PAINT message will leave the window filled with the background color
(note that in a professional paint application, we’d have to redraw the window
in the WM PAINT case). In addition, we can make the Exit item active by
borrowing code from the WM CLOSE case:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_NEW:
/* Place User Code to respond to the */
/* Menu Item Named "New" here. */
—> InvalidateRect(hWnd, NULL, 1);
—> SendMessage(hWnd, WM_PAINT, 0, 0L);
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
—> DestroyWindow(hWnd);
—» if (hWnd == hWndMain)
—> PostQuitMessage(0); /* Quit the application */
break;
Graphics and a Mouse-driven Paint Program 171
Now we’ve got to handle the Tools menu. When the user selects a drawing
tool, it should remain active until another tool is chosen, and we can do that
by setting a Boolean flag. For example, if the user selects the Line option, we
can set the Boolean flag bLine inside our program and leave it set until the
user chooses another drawing tool. When any part of our program needs to
know what drawing tool is currently active, it can check these flags. We can
define them as static BOOL like this (note that bPoint is initialized as TRUE
since the Point tool is active when the user opens our application):
That means that all we have to do when a Tools menu item is selected is to set
the correct flag. In addition, we have to move the check mark in the Tools
menu to the tool that the user chose, and we can do that with a function
named CheckOnlyMenuItem( ) like this:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_NEW:
/* Place User Code to respond to the */
/* Menu Item Named "New" here. */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
case IDM_T_POINT:
/* Place User Code to respond to the */
/* Menu Item Named "Point" here. */
—-> CheckOnlyMenuItem(hMenu, IDM_T_POINT);
bPoint = TRUE;
bDraw = FALSE;
bLine = FALSE;
bRectangle -= FALSE;
bEllipse = FALSE;
bFill = FALSE;
break;
172 Peter Norton’s QuickC for Windows
case IDM_T_DRAW:
/* Place User Code to respond to the */
/* Menu Item Named "Draw" here. */
—> CheckOnlyMenuItem(hMenu, IDM_T_DRAW);
bPoint = FALSE;
bDraw = TRUE;
bLine = FALSE;
bRectangle = FALSE;
bEllipse = FALSE;
bFill = FALSE;
break;
case IDM_T_LINE:
/* Place User Code to respond to the */
/* Menu Item Named "Line" here. */
case IDM_T_RECTANGLE:
/* Place User Code to respond to the */
/* Menu Item Named "Rectangle" here. */
—> CheckOnlyMenuItem(hMenu, IDM_T_RECTANGLE) ;
bPoint = FALSE;
bDraw = FALSE;
bLine = FALSE;
bRectangle = TRUE;
bEllipse = FALSE;
bFill = FALSE;
break;
case IDM_T_ELLIPSE:
/* Place User Code to respond to the */
/* Menu Item Named "Ellipse" here. */
case IDM_T_FILL:
/* Place User Code to respond to the */
/* Menu Item Named "Fill" here. */
Graphics and a Mouse-driven Paint Program 173
CheckOnlyMenuItem(hMenu, IDM_T_FILL);
bPoint = FALSE;
bDraw = FALSE;
bLine = FALSE;
bRectangle = FALSE;
bEllipse = FALSE;
bFill = TRUE;
break;
default:
return DefWindowProc(hWnd, Message, wParam, IParam);
}
break; /* End of WM_COMMAND */
#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2
174 Peter Norton’s QuickC for Windows
That’s all there is to this part of the program; now all we have to do is to check
the setting of these flags when we want to draw (i.e., when the left mouse
button goes up). In other words, the rest of the program is all mouse-driven,
and we only have to add code to the mouse events from now on. When the
mouse button goes down, we can check what kind of drawing operation we’re
supposed to perform by checking the flags, and when the button goes back
up, we can perform the operation itself. Let’s start by drawing individual
pixels.
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
—> SetPixel(hDC, LOWORD(lParam) , HIWORD(lParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
Note that we set the color of the pixel to black with the RGB( ) macro. This
macro returns a value of type COLORDEF, which is how Windows declares
colors. RGB( ) takes three parameters, each of which range from 0 to 255, and
each of which represent one of the primary color values (in order, red, green,
Graphics and a Mouse-driven Paint Program 175
File
V Point
Draw
Line
Rectangle
Ellipse
Fill
and blue). This is the way to design colors in Windows, with separate values for
the red, green, and blue components. Here, we’re setting our pixel to black —
i.e., all color components are 0: RGB(0, 0, 0). Note that these values can range
from 0 to 255; if, for example, we wanted a red dot, we could have specified
that as: RGB(255, 0, 0).
That’s it; when the Point tool is selected, as in Figure 5-3, and the left button
goes up in our client area, we’ll draw a point as shown in Figure 5-4.
The next tool in the Tools menu — and the next graphics operation we’ll
cover — is freehand drawing.
case WM_MOUSEMOVE:
if((wParam && MK_LBUTTON) && bDraw){
case WM_MOUSEMOVE:
if((wParam && MK_LBUTTON) && bDraw){
Graphics and a Mouse-driven Paint Program 177
hDC = GetDC(hWnd);
ReleaseDC(hWnd, hDC);
}
The next step is to draw on the screen. Although you might think we should
use SetPixel( ) here, mouse move events are not actually generated for every
pixel we pass over. Instead, a limited number of these events are generated
per second; if we simply drew a dot on the screen, we’ll end up with an
unconnected trail of pixels. Instead, we should store the previous mouse loca¬
tion and draw a line from that location to our current position. The effect on
the screen will be of a continuous path of set pixels, following the mouse
cursor in our window.
Unlike setting individual pixels, we need two points for a line. When the user
presses the left mouse button to start drawing, we can set one end of the line,
which we can call the anchor point. Next, the user moves the cursor, and we
should draw a line. In code, that means that we should set the anchor point
when the left button goes down (WM_LBUTTONDOWN) like this:
case WM_LBUTTONDOWN:
xAnchor = LOWORD(lParam);
yAnchor = HIWORD(lParam);
break;
ReleaseDC(hWnd, hDC);
}
And then we can draw the line connecting the dots with LineTo( ):
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
—» LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hDC);
}
Finally, we should update the anchor point so the next time a mouse move
event is generated, we’ll connect to the end of the line we just drew (note that
the anchor point is only updated if bDraw is TRUE —1 that is, if we’re drawing
freehand):
case WM_MOUSEMOVE:
if((wParam && MK_LBUTTON) && bDraw){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
And that’s it; when the user selects the Draw tool, they can hold the left mouse
button down and move the mouse cursor around, drawing as shown in Figure
5-5.
The next step, and the next drawing tool, is Line, which will allow the user to
draw lines on the screen. Let’s look into it immediately.
Drawing Lines
The usual way for the user to draw lines in a paint program is to select the Line
tool, press the left button at one point on the screen, move the cursor to the
other end of the line, and release the left button. At that time, the program
connects the two points; i.e., the location where the left button went down and
the location where it went up.
We already set the anchor point, which we’ll use as the first end of the line, in
the WM LBUTTONDOWN event:
case WM_LBUTTONDOWN:
xAnchor = LOWORD(1Param);
yAnchor = HIWORD(lParam);
break;
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hDC);
}
And that’s it. Now we can draw lines, as shown in Figure 5-6. We just select the
Line tool (setting bLine TRUE), press the left mouse button at some location
(setting the anchor point in the WM_LBUTTONDOWN case), move to the
final location, and release the mouse button (drawing the line with MoveTo()
and LineTo( ) in the WM_LBUTTONUP case).
180 Peter Norton5s QuickC for Windows
Paint
File Tools
There are more options, here, however; we can draw in different colors as
well.
We do not specify the drawing colors for lines in the same way that we do for
points (where we included a color value in the SetPixel( ) call). Instead, we
have to design a new pen. We draw figures in Windows with pens, and, as we’ll
see, we can fill the figures in with brushes. Once you place a pen into a device
context, it stays there until changed. To move a pen into a device context,
then, you use SelectObject( ) like this:
SelectObject(hDC, hPen)
Here, hPen is the handle of a pen. There are two common ways of obtaining
pen handles to pass to SelectObject( ): by getting a predefined pen with
GetStockObject( ),
SelectObject(hDC, GetStockObject());
Graphics and a Mouse-driven Paint Program 181
SelectObject(hDC, CreatePen());
To get a pen, you only pass one parameter to GetStockObject( ) — the type of
pen you want. Here are the predefined pens: BLACKPEN (the default),
NULL_PEN, and WHITEPEN. To install a white pen (and therefore to draw
figures in white), we would use this call:
SelectObject(hDC, GetStockObject(WHITE_PEN));
On the other hand, let’s say that we wanted to draw blue lines. In that case, we
could create a solid blue pen, one pixel wide with CreatePen ( ). CreatePen ( )
takes three parameters: a pen style; a pixel width for the pen; and a color. The
different pen styles, from solid to dotted, are shown in Figure 5-7. These are
(PS stands for pen style):
PSSOLID
PSDASH
PSDOT
PSDASHDOT
PSDASHD0TD01
PSNULL
PS INSIDEFRAME
TIP The last pen type above, PSINSIDEFRAME, is used when you’re drawing
closed figures with a border width greater than one pixel, but don’t want to
draw outside the figure. For example, if you set the pen style to
PS_INSIDEFRAME and then draw a rectangle with the Rectangle ( ) func¬
tion (which is coming up), the border line, no matter how thick, will stay
inside the rectangle’s boundary. The other pen styles would overlap outside
the rectangle.
CreatePen(PS_SOLID, 1...);
Finally, we have to specify the color of the pen. To create a solid blue pen, we
can do this:
You can specify white with RGB( ) like this: RGB(255, 255, 255). In addi¬
tion, the standard gray in Windows is actually RGB (128, 128, 128).
Now we can use SelectObject( ) to install this pen. SelectObject( ) returns the
handle of the object it is replacing, and it’s usually a good idea to store that
handle and reinstall it when you’re done. We can do that by declaring a
variable of type HPEN which we might call hPenOld:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
}
Graphics and a Mouse-driven Paint Program 183
When we’re done drawing, we can replace the original pen like this:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
hPenOld = SelectObject(hDC, CreatePen(PS_SOLID, 1, RGB(0/ 0, 255)))
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hPenOld);
ReleaseDC(hWnd, hDC);
This code would enable us to draw blue lines — now we’re drawing in color.
The next tool in our paint program is Rectangle, which will let us draw rectan¬
gles, a process which works much like drawing lines.
Drawing Rectangles
After the user selects the Rectangle tool, they press the mouse button at the
location of one corner, move the cursor to the other corner, and release it. At
that point, the program draws the rectangle. When drawing a line, we had to
use two points like this:
a--b
When drawing a rectangle, we also need to specify two points, like this:
-b
Here, however, we don’t have to use MoveTo( ) to set the current position;
instead, the Rectangle ( ) function can take the coordinates of both points at
once, like this:
This call draws a rectangle with the current pen from (ax, ay) to (bx, by). In
our case, the anchor point is set when the user presses the left mouse button,
and we can place the rectangle-drawing code in the WM_LBUTTONUP case.
In particular, the rectangle we want to draw goes from (xAnchor, yAnchor) to
(LOWORD (IParam), HIWORD(lParam)) since IParam holds the location of
the mouse cursor when the button went up. That means that we can draw
rectangles like this:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(IParam), HIWORD(IParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(IParam), HIWORD(IParam));
ReleaseDC(hWnd, hDC);
}
if(bRectangle){
hDC = GetDC(hWnd);
—> Rectangle(hDC, xAnchor, yAnchor, LOWORD(IParam), HIWORD(IParam));
ReleaseDC(hWnd, hDC);
}
It looks as though this should work — we’re passing the anchor point and the
point at which the left button went up to Rectangle ( ). However, there is a
problem. When Windows draws rectangles or other closed figures, it fills them
in with the background color by default, covering over what was there before.
That means that when the user draws a rectangle, anything behind it will be
obliterated, which is not the standard for paint programs.
Note that you can delete text by covering it with a filled rectangle like this.
You can use GetBkColor( ) to get the background color and create both a
pen and solid brush of that color. Then, using GetTextMetrics( ), you can
determine how high the current font is, and using GetTextExtent( ), you
can determine how long the string is you want to delete. Finally, you can
delete the text with Rectangle ( ).
In this case, we want to use NULL BRUSH, which insures that the figures are
not filled. Note that we save the old brush handle that we’re replacing, which
is of type HBRUSH (as you might have assumed), and restore it when we’re
done:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0) ) ;
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hDC);
}
if(bRectangle){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
Now we’re able to draw objects in our application without disturbing what was
underneath. However, there are one or two more points about brushes that
we should still cover. Just as we could create pens, so we can also create
brushes. In fact, there are two standard ways of creating brushes in Windows:
with CreateSolidBrush( ) and CreateHatchBrush ( ).
To create a solid brush, and therefore to fill with a solid color, we can use
CreateSolidBrush ( ), and pass it the color we want, like this to create a green
brush:
We can also create a hatch brush, which has a predefined pattern in it, with
CreateHatchBrush ( ). Here, we pass two parameters: the hatch style we want,
and the color we want. The allowed hatch styles are (HS stands for hatch
style): HS_HORIZONTAL, HS_VERTICAL, HS_FDIAGONAL, HS_BDIAGO-
NAL, HS_CROSS, and HS_DIAGCROSS, as shown in Figure 5-8. For example,
if we wanted to create a gray hatch brush of style HS_DIAGCROSS, we could
do that like this:
In this way, we can fill our figures with colored patterns if we want to.
You can even create your own brush patterns with the Windows functions
CreatePatternBrush( ) or CreateBrushIndirect( ).
At this point, we’re done with drawing rectangles. The next drawing tool in
our paint program is Ellipse, and, as we’ll see, drawing ellipses is very similar
to drawing rectangles.
Graphics and a Mouse-driven Paint Program 187
Drawing Ellipses
When the user selects the Ellipse tool, they can press the left mouse button,
setting an anchor point, move the cursor to a new location and then release
the mouse button. When they release the button, an ellipse will be drawn. We
can do that with the Ellipse( ) function, whose arguments are identical to the
Rectangle ( ) function. In this case, however, an ellipse is inscribed inside the
rectangle whose coordinates we pass. One corner of the rectangle will be
(xAnchor, yAnchor) and the other will be (LOWORD (IParam),
HIWORD(lParam)). To do this, we simply check if the bEllipse flag is set in
the WM_BUTTONUP case, and, if it is, proceed as if we were drawing a
rectangle — but use Ellipse ( ) instead:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(IParam), HIWORD(IParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(IParam), HIWORD(IParam));
ReleaseDC(hWnd, hDC);
}
if(bRectangle){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, xAnchor, yAnchor, LOWORD(IParam), HIWORD(IParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
if(bEllipse){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Ellipse(hDC, xAnchor, yAnchor, LOWORD(IParam), HIWORD(IParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
Note that we install a NULL BRUSH before drawing the ellipse so that the
background graphics is not covered over. At this point, then, we’re able to add
ellipses to our paint program, as shown in Figure 5-9.
188 Peter Norton’s QuickC for Windows
There is one last painting tool that we placed in our Tools menu, and that is
Fill, which will allow us to fill figures in. Let’s look at that next.
The bounding color is the color of the border of the figure that we’re filling
in, and we pass that color so that FloodFill( ) knows when to stop filling. For
example, the figures we’ve drawn so far have been black, so the bounding
colors of our figures is black. FloodFill( ) uses the device context’s current
brush, so let’s fill in our figures with black as well by selecting a black brush:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(IParam), HIWORD(IParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
Graphics and a Mouse-driven Paint Program 189
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hDC);
}
if(bRectangle){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
if(bEllipse){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Ellipse(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
if(bFill){
hDC = GetDC(hWnd);
ReleaseDC(hWnd, hDC);
}
break;
Next, we just fill the figure with FloodFill( ) (note that we’re indicating that
the bounding color is black, RGB(0, 0, 0)), and restore the previous brush:
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hDC);
}
if(bRectangle){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
190 Peter Norton’s QuickC for Windows
}
if(bEllipse){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Ellipse(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
if(bFill){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(BLACK_BRUSH));
And that’s it; now we can fill in our figures, as shown in Figure 5-10.
Our paint program is done, except for a few embellishments that we might
add. For example, it’s customary for paint programs to show lines, rectangles,
or ellipses as they’re being sized, giving the user the illusion of “stretching” the
figure into shape. And we can do that too.
To do this, we’ll have to store the end point of the previous line so that we can
erase it before drawing new ones. In other words, the process will go like this:
4. Repeat step 3 as the user moves the mouse around, giving the impres¬
sion of stretching the line. When we get a WM_LBUTTONUP message,
we should draw the final line.
Note that this means we have to erase the old line before we draw the new one
as the mouse cursor moves around. In addition, we should make sure that the
line we’re stretching is visible on the screen — if we just stretch a black line,
for example, it will disappear as we move over a black filled figure. One way to
do this is to specify the current pen’s drawing mode. This indicates the way the
current pen will interact with what’s on the screen in a bit-by-bit fashion, and
the options range from ignoring what’s already on the screen entirely and
simply drawing with the pen to ignoring the pen entirely and leaving the
screen alone. There are 16 different options, and they appear in Table 5-1
(the R2 prefix stands for binary raster operation).
192 Peter Norton’s QuickC for Windows
BLACK R2_BLACK
-(PEN ! SCREEN) R2_N OTMERGEPEN
-PEN 8c SCREEN R2_MASKN OTPEN
-PEN R2_NOTCOPYPEN
PEN & -SCREEN R2_MASKPENN OT
-SCREEN R2_NOT
PEN A SCREEN R2_XORPEN *
-(PEN & SCREEN) R2_N OTMASKPEN
PEN & SCREEN R2_MASKPEN
-(PEN A SCREEN) R2_NOTXORPEN
SCREEN R2_NOP
-PEN ! SCREEN R2_MERGEN OTPEN
PEN R2_COPWEN [the default]
PEN ! -SCREEN R2_MERGEPENN OT
PEN I SCREEN R2_MERGEPEN
WHITE R2_WHITE
Let’s use the R2_NOT drawing mode, which simply inverts what’s on the
screen in a bit-by-bit fashion. That is, when we draw with a R2_NOT pen, white
(RGB(255, 255, 255)) will become black (RGB(0, 0, 0)), and black will be¬
come white. In addition, when we draw the same line over again with an
R2_NOT pen, overwriting the first time, the original screen pixels will be
restored, and that’s what we want. In other words, we can rewrite our four
steps above to use the R2_NOT pen like this:
4. Repeat step 3 as the user moves the mouse around, giving the impres¬
sion of stretching the line. When we get a WM LBUTTONUP message,
we should draw the final line with R2_COPYPEN.
To stretch the line, then, we can add code to the WMMOUSEMOVE case. We
do that first by checking if the left mouse button is down (i.e., if (wParam ScSc
MK_LBUTTON) is TRUE), and if we’re actually drawing lines (i.e., if bLine is
TRUE). If so, we get a device context handle:
case WM_MOUSEMOVE:
ReleaseDC(hWnd, hDC);
}
Now we get the old drawing mode with the Windows function GetROP2( )
(i.e., Get binary raster operation mode) and save it in an integer variable we
can call nDrawMode, then set the drawing mode to R2_NOT with SetROP2( ):
case WM_MOUSEMOVE:
ReleaseDC(hWnd, hDC);
Next, we have to erase the old line from the previous WM MOUSEMOVE
event before drawing the new one. The old line runs from (xAnchor,
yAnchor) to (xold, yold).
Note that (xold, yold) must hold a valid point from the very beginning of the
drawing operation, so we set it to (xAnchor, yAnchor) when the left button
originally goes down in the WM LBUTTONDOWN case:
case WM_LBUTTONDOWN:
xAnchor = LOWORD(1Param);
194 Peter Norton’s QuickC for Windows
yAnchor = HIWORD(lParam);
We just erase the old line by drawing it again with the drawing mode set to
R2_NOT, and draw the new line from the anchor point to our current posi¬
tion like this:
case WM_MOUSEMOVE:
ReleaseDC(hWnd, hDC);
}
All that remains now is to update (xold, yold) for the next time, and reset the
drawing mode:
case WM_MOUSEMOVE:
yold = HIWORD(lParam);
^ SetR0P2(hDC, nDrawMode);
ReleaseDC(hWnd, hDC);
}
Graphics and a Mouse-driven Paint Program 195
And that’s all there is to it. Now when we set the anchor point and move
around the screen while drawing lines, we stretch a line from the anchor point
to the current cursor position, as in Figure 5-11.
We can do the same thing for ellipses and rectangles easily; all we have to do
is to set the drawing mode to R2_NOT, draw over old rectangles as the cursor
moves around the screen and draw new ones, just as we did for lines. That
looks like this in the WM MOUSEMOVE case:
case WM_MOUSEMOVE:
if((wParam && MK_LBUTTON) && bDraw){ // Draw Mode
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
xAnchor = LOWORD(lParam);
yAnchor = HIWORD(lParam);
ReleaseDC(hWnd, hDC);
}
if((wParam && MK_LBUTTON) && bLine){
hDC = GetDC(hWnd);
nDrawMode = GetR0P2(hDC);
SetR0P2(hDC, R2_N0T);
MoveTo(hDC, xAnchor, yAnchor);
196 Peter Norton’s QuickC for Windows
And that’s all there is to it. The entire listing of paint.c appears in Listing 5-1.
/***********************************************************************/
(continued)
198 Peter Norton’s QuickC for Windows
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
static HMENU hMenu= 0; /* handle for the menu */
switch (Message)
{
Graphics and a Mouse-driven Paint Program 199
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_NEW:
/* Place User Code to respond to the */
/* Menu Item Named "New" here. */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
case IDM_T_POINT:
/* Place User Code to respond to the */
/* Menu Item Named "Point" here. */
CheckOnlyMenuItem(hMenu, IDM_T_POINT);
bPoint = TRUE;
bDraw = FALSE;
bLine = FALSE;
bRectangle = FALSE;
bEllipse = FALSE;
bFill = FALSE;
break;
case IDM_T_DRAW:
/* Place User Code to respond to the */
/* Menu Item Named "Draw" here. */
CheckOnlyMenuItem(hMenu, IDM_T_DRAW);
bPoint = FALSE;
bDraw = TRUE;
bLine = FALSE;
bRectangle = FALSE;
bEllipse = FALSE;
bFill = FALSE;
break;
(continued)
200 Peter Norton’s QuickC for Windows
case IDM_T_LINE:
/* Place User Code to respond to the */
/* Menu Item Named "Line" here. */
CheckOnlyMenuItem(hMenu, IDM_T_LINE);
bPoint = FALSE;
bDraw = FALSE;
bLine = TRUE;
bRectangle = FALSE;
bEllipse = FALSE;
bFill = FALSE;
break;
case IDM_T_RECTANGLE:
/* Place User Code to respond to the */
/* Menu Item Named "Rectangle" here. */
CheckOnlyMenuItem(hMenu, IDM_T_RECTANGLE);
bPoint = FALSE;
bDraw = FALSE;
bLine = FALSE;
bRectangle = TRUE;
bEllipse = FALSE;
bFill = FALSE;
break;
case IDM_T_ELLIPSE:
/* Place User Code to respond to the */
/* Menu Item Named "Ellipse" here. */
CheckOnlyMenuItem(hMenu, IDM_T_ELLIPSE);
bPoint = FALSE;
bDraw = FALSE;
bLine = FALSE;
bRectangle = FALSE;
bEllipse = TRUE;
bFill = FALSE;
break;
case IDM_T_FILL:
/* Place User Code to respond to the */
/* Menu Item Named "Fill" here. */
CheckOnlyMenuItem(hMenu, IDM_T_FILL);
bPoint = FALSE;
bDraw = FALSE;
bLine = FALSE;
bRectangle = FALSE;
bEllipse = FALSE;
bFill = TRUE;
Graphics and a Mouse-driven Paint Program 201
break;
default:
return DefWindowProc(hWnd, Message, wParam, IParam);
}
break; /* End of WM_COMMAND */
case WM_LBUTTONDOWN:
xAnchor = LOWORD(IParam);
yAnchor = HIWORD(IParam);
xold = xAnchor;
yold = yAnchor;
break;
case WM_MOUSEMOVE:
if((wParam && MK_LBUTTON) && bDraw){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(IParam), HIWORD(IParam));
xAnchor = LOWORD(IParam);
yAnchor = HIWORD(IParam);
ReleaseDC(hWnd, hDC);
}
if((wParam && MK_LBUTTON) && bLine){
hDC = GetDC(hWnd);
nDrawMode = GetROP2(hDC);
SetROP2(hDC, R2_NOT);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, xold, yold);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(IParam), HIWORD(IParam));
xold = LOWORD(IParam);
yold = HIWORD(IParam);
SetROP2(hDC, nDrawMode);
ReleaseDC(hWnd, hDC);
}
if((wParam && MK_LBUTTON) && bRectangle){
hDC = GetDC(hWnd);
nDrawMode = GetROP2(hDC);
SetROP2(hDC, R2_NOT);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, xold, yold, xAnchor, yAnchor);
Rectangle(hDC, xAnchor, yAnchor, LOWORD(IParam), HIWORD(IParam));
SelectObject(hDC, hBackup);
xold = LOWORD(IParam);
(continued)
202 Peter Norton’s QuickC for Windows
yold = HIWORD(lParam);
SetROP2(hDC, nDrawMode);
ReleaseDC(hWnd, hDC);
}
if((wParam && MK_LBUTTON) && bEllipse){
hDC = GetDC(hWnd);
nDrawMode = GetROP2(hDC);
SetROP2(hDC, R2_NOT);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Ellipse(hDC, xold, yold, xAnchor, yAnchor);
Ellipse(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
xold = LOWORD(lParam);
yold = HIWORD(lParam);
SetROP2(hDC, nDrawMode);
ReleaseDC(hWnd, hDC);
}
break;
case WM_LBUTTONUP:
if(bPoint){
hDC = GetDC(hWnd);
SetPixel(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0));
ReleaseDC(hWnd, hDC);
}
if(bLine){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
ReleaseDC(hWnd, hDC);
}
if(bRectangle){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
if(bEllipse){
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(NULL_BRUSH));
Ellipse(hDC, xAnchor, yAnchor, LOWORD(lParam), HIWORD(lParam));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
if(bFill){
Graphics and a Mouse-driven Paint Program 203
hDC = GetDC(hWnd);
hBackup = SelectObject(hDC, GetStockObject(BLACK_BRUSH));
FloodFill(hDC, LOWORD(lParam), HIWORD(lParam), RGB(0, 0, 0));
SelectObject(hDC, hBackup);
ReleaseDC(hWnd, hDC);
}
break;
case WM_CREATE:
hMenu = GetMenu(hWnd);
break; /* End of WM_CREATE */
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
(continued)
204 Peter Norton’s QuickC for Windows
Listings-!, (continued)
} /* End of WndProc */
/
/ */
/ nCwRegisterClasses Function */
/ */
/ * The following function registers all the classes of all the windows */
/ * associated with this application. The function returns an error code */
/ * if unsuccessful, otherwise it returns 0. */
/ * * /
/ ************************************************************************/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset (Scwndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************ i
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/************************************************************************/
void CwUnRegisterClasses(void)
Graphics and a Mouse-driven Paint Program 205
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
}
NOTE To develop our paint program into a full Windows application, we’d have
to handle the WM_PAINT case. That message is sent to us when part of our
window is uncovered or has to be redrawn for any reason. Since we don’t
know exactly what the user has already drawn, we’d have to restore the
entire image in our client area each time this happens. One way to do this
is to set up a bitmap with CreateBitMap( ) and make sure that anything we
draw is also drawn in the bitmap. That way, the bitmap will hold a copy of
what’s on the screen. When we get a WM_PAINT message, we could then
transfer the image from the bitmap to the screen using the fast bit transfer
function, BitBlt( ).
Just click the OK button, indicating that we want to create a 4-Plane 16-COL¬
ORS 32x32 pixel icon. When you do, two new boxes appear in the Image
Editor: In the left box, we can click and change pixels; in the right box, we see
what our icon will look like. Since this is a paint program, let’s design a
paintbrush-like icon, as shown in Figure 5-14.
\
Left Right
® color wmnn
Q Screen I 1
O Inverse
When done, we save the icon as the file paint.ico, using the Save As... item in
the Image Editor’s File menu. Now we’re ready to integrate it into our pro¬
gram. Do that by bringing up QuickCase:W and selecting the Icon... item in
the Design menu. A dialog box appears, asking you to select an icon file. Just
select paint.ico and click OK. Then update the paint.mak project by selecting
Update in the Build menu and rebuild the paint program’s files. Next, in the
QuickC for Windows development enviroment, rebuild paint.exe. Finally, add
it to a program group in the Windows Program Manager by using the New...
item in its File menu. The resulting icon appears as shown in Figure 5-15, and,
when we click it, our paint program starts.
That completes our coverage of drawing graphics in Windows; next, let’s start
looking into dialog boxes.
Microsoft QC/Win
Micros
s-n»
U U Av'in QuickCa$e:W
113 V
Dialog Editor Image Editor Paint Program
We' ve all seen dialog boxes; in Windows, they represent the standard way of
receiving control input from the user beyond the menu level. The Windows
user uses dialog boxes to open files, rename files, customize windows or appli¬
cation parameters, select colors, or almost anything that a simple menu selec¬
tion can’t specify. Dialog boxes are so common that the user already knows
that menu items with an ellipsis (three dots) after them, like Save As..., open
dialog boxes.
In this chapter, we’ll start putting together dialog boxes of our own, and we’ll
start seeing the kind of objects that the user can manipulate in dialog boxes —
specifically, buttons and text boxes. These types of objects are called controls,
and we’ll be seeing a lot of them in the following chapters (controls include:
buttons, text boxes — which are also called edit controls — scroll bars, list
boxes, and so on). Designing dialog boxes used to be difficult, but it’s been
made a lot easier with the Dialog Editor utility, which is included in QuickC
for Windows. To a large extent this and the next chapter will be an explora¬
tion of that utility. Let’s start immediately with a quick way to place dialog
boxes on the screen.
209
210 Peter Norton’s QuickC for Windows
Message Boxes
The quickest way to put a simple dialog box on the screen is with the Windows
MessageBox( ) function:
Here, hWnd is the handle of the message box’s parent window, lpText is a far
pointer to the text we want in our message box, lpCaption is a far pointer to
the caption text we want to appear in the message box’s title bar (if this is
NULL, the default caption is “Error,” which gives you an idea of what Win¬
dows expects you to use message boxes for), and wType specifies the controls
you can have in your message box, as we’ll see.
This function lets you design a dialog box to a certain extent, and allows the
user to communicate to your program through buttons: OK, Cancel, Abort,
Retry, Ignore, Yes, or No. To see how MessageBox( ) works, we might create a
small program that places a window on the screen with a menu item named
Message Box... in the File menu, as shown in Figure 6-1 (note that we’re
including three dots after the name Message Box to indicate that a dialog box
will appear). When we generate the code for this window using QuickCase:W,
this is the part of the code that handles the Message Box... item (from
WndProc( )):
case WM_COMMAND:
switch (wParam)
{
case IDM_F_MESSAGEBOX:
/* Place User Code to respond to the */
/* Menu Item Named "Message Box..." here. */
break;
Message Box.
case WM_COMMAND:
switch (wParam)
{
case IDM_F_MESSAGEBOX:
/* Place User Code to respond to the */
/* Menu Item Named "Message Box..." here. */
—> MessageBox(hWnd, (LPSTR) "This is a message box." \
—> (LPSTR) "Message Box", MB_OKCANCEL ! \
—> MBICONEXCLAMATION) ;
break;
Note the MB (message box) constants that make up the last parameter:
MB_OKCANCEL ! MBICONEXCLAMATION. In general, this is how we
specify what kinds of controls we want in our message box. MB OKCANCEL
indicates that we want an OK button and a Cancel button, and
MBICONEXCLAMATION indicates that we want an exclamation point icon.
The different message box types, which can be ORed together and then
passed to MessageBox ( ), appear in Table 6-1. A few of them deserve special
attention. MB DEFBUTTON1, for example, makes the first button in the
mssage box the default. When a button is the default, it is surrounded by a
black border to indicate that if the user presses Enter, that button will be
selected.
Almost every dialog box that has buttons should have a Cancel button in it
because Windows users expect them. Cancel buttons allow users to close
the dialog box without making changes in the course of the rest of the
program.
212 Peter Norton's QuickC for Windows
wType Means
Our message box appears as in Figure 6-2. As you can see, we’ve set up the
message box’s caption, internal text, buttons (OKand Cancel), and added an
exclamation icon. It was that easy. When the user clicks one of the buttons or
closes the message box, we get an integer return value corresponding to one
of these constants (as defined in windows.h):
Checking the return value of MessageBox( ) against this list will allow you to
determine what action the user took.
Dialog Boxes: Buttons and Textboxes 213
Message Box
OK Cancel
mmm
Start by clicking the Dialog Editor icon in the QuickC for Windows program
group. The Dialog Editor appears, as in Figure 6-3. For us, the most important
part of the Dialog Editor will be the toolbox, which appears in Figure 6-4. With
the toolbox, we’ll be able to create the controls that users have come to expect
in dialog boxes; each of the tools in the toolbox is labeled in Figure 6-4,
indicating what kind of control it is used to create.
Let’s begin the design process by creating our own dialog box. To do that,
select the New item in the Dialog Editor’s File menu. A new dialog box tem¬
plate appears in the Dialog Box’s window as shown in Figure 6-5.
214 Peter Norton’s QuickC for Windows
We can resize this new dialog box with the small black tabs you see around the
perimeter of the new dialog box, called sizing handles. Using the sizing han¬
dles, you can stretch the dialog box into the shape you require. The first thing
we can do is to give the dialog box a new title; currently, that’s set to Dialog
Title, both in the caption of the dialog box itself, and in the Caption box
above it. To change that, edit the Caption box and change the title to, say,
Dialog Box, since this is our first dialog box.
Now we’re ready to start adding controls. First, click the push button tool in
the toolbox (second tool down on the right), position the button outline that
will follow the mouse cursor in the upper left of the new dialog box, and click.
A new button appears, as in Figure 6-6.
Change the text in this button from its default, Push, to, say, Push Me simply
by editing the Caption box above it (which is now renamed the Text box) and
pressing Enter. The button’s text now becomes “Push Me,” as shown in Fig¬
ure 6-7.
Note that there is now a number associated with this button, as can you see in
the upper right-hand corner of the Dialog Editor — the number is 101. This
is the button’s ID number, and we’ll see it later; when the button is clicked, for
example, 101 will be passed to us in wParam so we know which control is
sending us a message. Each control we add to our dialog box will have an ID
like this (the dialog box itself is number 100). We can also associate a symbol
(i.e., a text string) with controls like these instead of numbers like 101, and
you can see the Symbol box above the Text text box in Figure 6-7.
2
* =1 Dialog Box
A [ab i ■
n c) ■
Push t
6
m @
S 1
|- -
3
♦3 I
□ ■[
J
1 -' ■
Figure 6-6. Adding a Button to Our Dialog Box.
- J
1 Dialog Box
j
A |ab ■ g m
nc ) ■
Push Me ^
m «)
1
03 i
i ■ i
j
1 - ■
Figure 6-7. Changing a Button’s Text.
Dialog Boxes: Buttons and Textboxes 217
pie, double clicking the button Push Me now brings up the dialog box shown
in Figure 6-8, showing the options available for buttons.
In fact, we can double click the new dialog box itself to see what other options
are available for it, as shown in Figure 6-9. Here you can select what font is
used, what the dialog box’s border will be like, and many other options.
In our case, we want a few more controls: we need a text box to display the
string “Hello, world.”, and we should also add OK and Cancel buttons, since
most dialog boxes have them. To add a text box, simply click the text box tool
in the toolbox (the first tool down on the right), position the text box outline
in the upper right-hand corner of the new dialog box, and click. Next, add two
buttons, labeling them OK and Cancel (using the Text text box), as shown in
Figure 6-10. The ID numbers for these new controls are text box: 102; OK
218 Peter Norton’s QuickC for Windotus
File Edit
Dialog Editor - (Untitled)*, (Untitled)
Arrange Options Help
331
Dialog Styles
OK Cancel Help
SC/
El
:-r-; **
■ ■ -1
Dialog Box
A ab
□ □
Push Me
■ a
IM
OK | Cancel
*K
□ i_ _i _ _a
<0 □
Figire 6-10. Complete Dialog Box Template.
Dialog Boxes: Buttons and Textboxes 219
button: 103; and Cancel button: 104. We’ll need to keep these numbers in
mind for later use.
Now we can save some time by indicating to the Dialog Editor that the buttons
we’ve just added are supposed to function as OK and Cancel buttons.
QuickCase:W can program OK and Cancel buttons for us automatically, put¬
ting in much of the correct code for these buttons by itself. To indicate that a
button is either an OK or Cancel button, click the down arrow in the Dialog
Editor’s ID box, as shown in Figure 6-11. Select the IDOK option for the OK
button, and IDCANCEL for the Cancel button. This changes the ID number
for the OK button to IDOK and the ID for the Cancel button to IDCANCEE.
When QuickCase:W sees these values later, it will know that we want it to
produce additional code to handle these particular buttons.
Now we’re ready to generate the dialog box itself. The file that the Dialog Box
editor produces is a .dig file, which is then read by QuickCase:W. We can call
our .dig file, say, dialog.dlg by selecting the Save As... item in the Dialog
Editor’s File menu and giving the name dialog.dlg. When we do, dialog.dlg is
created. This file holds the complete specification for our dialog box, like this:
220 Peter Norton’s QuickC for Windows
That’s all there is to it — you can see that all of our controls are here, along
with their IDs, positions, and sizes. Now we can close the Dialog Editor and
start QuickCase:W. In QuickCase:W, we can give our new window the title
Dialog Box Example. Add a File menu with two items in it as shown in Figure
6-12: Dialog Box... and Exit. We can connect our dialog box (i.e., dialog.dig)
to the Dialog Box... item in our new File menu by clicking that item and by
filling in the Insert Menu Item dialog box which appears.
In the Insert Menu Item dialog box, click the down arrow in the Link To box,
as shown in Figure 6-13. This indicates what we can connect our menu item to;
in our case, we want to connect it to a dialog box, so select that option. Next,
click the Configure Link... button in the Link To box, and a new dialog box
appears, as in Figure 6-14. Here, we have to specify which .dig file we want to
associate with our menu item Dialog Box..., and we do that simply by selecting
dialog.dlg and clicking OK.
QuickCase:W - (MYDLG.WIN)
File Edit Design Build Tools Options Help
E File |
Dialog Box Program 33
Dialog Box...
Exit
«»
Accelerator:
Initial State
O Grayed
Link to
Dialog Box
None
m
Dropdown Menu
Dialog Box
d User-Defined Code
b
Figure 6-13. QuickCase:W’s Link to Box.
NOTE At this point, we can also indicate whether the dialog box is to be modal or
nonmodal; here we’ll stick with the more common modal dialog boxes,
which means that the user must finish with them before continuing work.
Now we can generate our .c file and prepare to run the program. To do that,
name the project, say, mydlg.win with the Save As... item in QuickCase:W’s File
menu. Next, select the Generate item in QuickCaseiW’s Build menu to create
mydlg.c, mydlg.h, and the other files needed to create mydlg.exe.
When the generation is complete, bring up the QuickC for Windows environ¬
ment and open the mydlg.mak project (from the Project menu), as well as the
file mydlg.c (from the File menu). Since we’ve linked our dialog box to the
Dialog Box... menu item, the code to place the dialog box on the screen
already appears in the IDM_F_DIALOGBOX case in WndProcn( ):
222 Peter Norton’s QuickC for Windows
switch (Message)
{
case WM_C OMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_DIALOGBOX:
/* Place User Code to respond to the */
/* Menu Item Named "Dialog Box..." here. */
{
FARPROC lpfnDIALOGMsgProc;
lpfnDIALOGMsgProc = MakeProcInstance((FARPROC)DIALOGMsgProc,\
—> hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
—> lpfnDIALOGMsgProc);
—> FreeProcInstance(lpfnDIALOGMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
break;
Dialog Box
O Modeless
In other words, when the user selects the Dialog Box... item in our File menu,
the above code (including the Windows function DialogBox( )) will be exe¬
cuted, displaying our dialog box. All we need to do now is to borrow code
from the WM CLOSE case and add it to our File menu’s Exit item like this:
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_DIALOGBOX:
/* Place User Code to respond to the */
/* Menu Item Named "Dialog Box..." here. */
{
FARPROC lpfnDIALOGMsgProc;
lpfnDIALOGMsgProc = MakeProcInstance((FARPROC)DIALOGMsgProc, \
hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
lpfnDIALOGMsgProc);
FreeProcInstance(lpfnDIALOGMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
// USER added code
—> DestroyWindow(hWnd) ;
—> if (hWnd == hWndMain)
—> PostQuitMessage(0) ; /* Quit the application */
—> break;
Now our dialog box is active — that is, it will appear on the screen when we
click the Dialog Box... item. Note that the code above passes the address of a
new function, DIALOGMsgProc ( ), to Windows, and then calls the
DialogBox( ) function, which displays the dialog box:
switch (Message)
{
case WM_C OMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
224 Peter Norton’s QuickC for Windows
case IDM_F_DIALOGBOX:
/* Place User Code to respond to the */
/* Menu Item Named "Dialog Box..." here. */
{
FARPROC IpfnDIALOGMsgProc;
—> lpfnDIALOGMsgProc = MakeProcInstance((FARPROC)DIALOGMsgProc, \
—> hlnst);
—» IpfnDIALOGMsgProc);
FreeProcInstance(IpfnDIALOGMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
This new function, DIALOGMsgProc ( ), handles all the messages to our dia¬
log box — that is, it is a window procedure for the dialog box (and the dialog
box, of course, is just another window). That procedure looks like this, as
programmed for us by QuickCaseiW:
/
*/
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/************************************************************************/
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
Dialog Boxes: Buttons and Textboxes 225
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, OL);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: /* Button text: "Push Me" */
break;
case 102: /* Edit Control */
break;
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of DIALOGMsgProc */
The correct code for the OK and Cancel buttons is already in place here; if the
user selects OK, we end the dialog box with the function EndDialog ( ), and
return a value of TRUE to the function DialogBox( ) back in WndProc( )
(which we can test):
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
On the other hand, if the user clicks the Cancel button, we end the dialog box
and return a value of FALSE to DialogBox( ):
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
226 Peter Norton’s QuickC for Windows
In our case, we want to place the text “Hello, world.” in the text box (ID = 102)
when the user clicks the button marked Push Me (ID = 101). Just like
WndProc( ), DIALOGMsgProc ( ) has a case in its switch statement to handle
WM_COMMAND messages to our message box. In this case, wParam holds
the ID of the control sending us the message:
break;
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
When the user clicks the Push Me button, we can place the string “Hello,
world.” in the text box with the Windows SetDlgItemText( ) function. That
function takes three parameters. The first is the handle of the dialog box
itself, which is passed to us in DIALOGMsgProc ( ) as hWndDlg:
Next, we have to indicate which control we’re addressing in the dialog box,
and we do that by passing the text box’s ID number, 102. Finally, we pass a
long pointer to the string itself, like this:
case WM_COMMAND:
switch(wParam)
{
case 101: /* Button text: "Push Me" */
// USER added code.
Dialog Boxes: Buttons and Textboxes 227
And that’s it; we’re ready to run (note that we only had to add one line of code
to the dialog box procedure). Just build mydlg.exe and run it in the QuickC
for Windows environment. Our window appears on the screen; click the Dia¬
log Box... item in the File menu to bring up our dialog box, then click the
“Push me” button. When you do, the text “Hello, world.” appears in the text
box, as shown in Figure 6-15. The entire program, mydlg.c, appears in Listing
6-1. We might also note that mydlg.h was automatically updated to include
DLALOGMsgProc( )’s prototype, as we can see in Listing 6-2. In addition,
mydlg.rc, shown in Listing 6-3, now includes the dialog.dig file explicitly with
the line:
#include "MYDLG.h"
MYDLG MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Dialog Box...", IDM_F_DIALOGBOX
MENUITEM "Exit", IDM_F_EXIT
END
END
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class
END
[ 1 Cancel
6
strcpy(szAppName, "MYDLG");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
Dialog Boxes: Buttons and Textboxes 229
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
(continued)
230 Peter Norton's QuickC for Windows
/ ************************************************************************ /
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
»
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_DIALOGBOX:
/* Place User Code to respond to the */
/* Menu Item Named "Dialog Box..." here. */
{
FARPROC lpfnDIALOGMsgProc;
lpfnDIALOGMsgProc = MakeProcInstance((FARPROC)DIALOGMsgProc,\
hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd,\
lpfnDIALOGMsgProc);
FreeProcInstance(lpfnDIALOGMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
// USER added code
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
Dialog Boxes: Buttons and Textboxes 231
}
break; /* End of WM_COMMAND */
case WM_CREATE:
break; /* End of WM_CREATE */
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/**************************** ********************************************/
/* */
/* Dialog Window Procedure */
/* */
(continued)
232 Peter Norton’s QuickC for Windows
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM__COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: /* Button text: "Push Me" */
//USER added code.
SetDlgltemText(hWndDlg, 102, (LPSTR) "Hello, world.");
break;
case 102: /* Edit Control */
break;
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End Of WM_COMMAND */
default:
return FALSE;
}
Dialog Boxes: Buttons and Textboxes 233
return TRUE;
} /* End of DIALOGMsgProc */
/************************************************************************ /
/* *
/
/* nCwRegisterClasses Function */
j* *
/
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code * /
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************ /
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses * /
/************************************************************************ /
/* cwCenter Function * /
/* * /
/* centers a window based on the client area of its parent * /
/* * /
/************************************************************************ /
(continued)
234 Peter Norton's QuickC for Windows
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* */
void CwUnRegisterClasses(void)
{
Dialog Boxes: Buttons and Textboxes 235
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
ttdefine IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2
MYDLG MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Dialog Box...", IDM_F_DIALOGBOX
MENUITEM "Exit", IDM_F_EXIT
END
END
(continued)
236 Peter Norton’s QuickC for Windows
#include "DIALOG.DLG"
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END
A Calculator Example
Now that we know the details, we can work more quickly. Here are the general
steps for adding a dialog box:
1. Design the dialog box with the Dialog Editor and Save it, producing the
.dig file (one dialog box per .dig file).
3. Edit the .c file, making changes as needed to the dialog box function to
make your controls active.
Let’s follow these steps and work with the controls we’re familiar with so far —
buttons and text boxes — to create a new example, a simple pop-up calculator
that will let us add numbers. Start the Dialog Editor once again and give a new
dialog box the caption Calculator. Next, add two text boxes as shown in Figure
6-16.
Now add a push button with the caption “=” under the text boxes, and, using
the Text tool (first tool down on the left), place a plus sign, +, between the text
boxes, as shown in Figure 6-17.
+- text box
text box
button
Dialog Boxes: Buttons and Textboxes 237
Calculator
A ab
□ □
2*
□
□
Figure 6-16. Calculator Template with Two Text Boxes.
The user can place numbers in the two text boxes when the dialog box is
active:
12
The next step is to display the answer when they click the = button:
12
Click here
Answer
Note, however, we don’t want the answer to appear in a text box because text
boxes can be edited by the user; it would be more appropriate to display the
answer as simple text, which we can change but which is not accessible to the
user. To do that, first draw a rectangle using the Rectangle tool (sixth tool
down on the left) so that the answer box looks like the text box controls above
it. Next, click the Text tool, use the sizing handles that appear so that the text
fills the rectangle, and place a 0 in it as shown in Figure 6-18. This text is
actually a control, and we’ll be able to change it from inside our program.
Now select the Save As... item in the File menu, and call this dialog box
calc.dig. That file looks like this (note that all our controls are here, including
the text fields):
A ab
□a
H*
□
□
Figure 6-18. Completed Calculator Template.
BEGIN
EDITTEXT 101, 38, 8, 32, 12, ES_AUTOHSCROLL
EDITTEXT 102, 38, 47, 32, 12, ES_AUTOHSCROLL
PUSHBUTTON "=", 103, 38, 67, 32, 14
LTEXT " + ", 106, 51, 29, 9, 8
CONTROL 104, "Static", SS_BLACKFRAME, 40, 88, 30, 15
LTEXT "0", 105, 41, 90, 27, 11
END
Next, start QuickCasefW and create a new program called mycalc.win, with a
caption of, say, Calculator Example and a File menu with two items in it:
Calculator... and Exit. Connect calc.dig to the Calculator... item with the In¬
sert Menu Item’s Configure Link... button, as shown in Figure 6-19. Call this
project mycalc.mak and create it with the Generate item in QuickCase:W’s
Build menu.
At this point, we’re ready to make our calculator active. This time,
QuickCase:W has called our dialog box procedure CALCMsgProc( ) (it cre¬
ates the names based on the names of the .dig files you link in), and it looks
like this:
240 Peter Norton’s QuickC for Windows
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: /* Edit Control */
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of CALCMsgProc */
All the action occurs when the user clicks the button marked when they
do, we’re supposed to take the numbers in the two text boxes, add them, and
Dialog Boxes: Buttons and Textboxes 241
Dialog Box
File Name: Directories: □K
calc, dig c:\qcwin\bin
calc, dig Cancel
dialog, dig
dialogs, dig
dlgs3.dlg Help
menuchek.dlg
notepad, dig
testing, dig
O Modeless
j j ~~
display them in the answer box. The controls we have, and their IDs, are set up
like this:
12 101
102
103
104
First, we must get the two operands that we re supposed to add from the two
text boxes, ID numbers 101 and 102. We can do that by setting aside two
strings, one for each operand, like this:
break;
Now we have to convert from text to numbers; let’s make them integers, which
will let us use the C function atoi( ). We set aside space for the two integers,
which we can call nOpl and nOp2, like this:
break;
At this point, we have the two numbers to add, nOpl and nOp2, so we can add
them and store them in a new string, which we can call szResult:
break;
It only remains to display szResult in the answer text field (whose ID is 105),
and we can do that like this:
And we’re done; run the program in the QuickC for Windows environment,
and give it a try, as in Figure 6-20. When you bring the calculator up, you can
plug (integer) values into the two text boxes, click the = button, and see the
answer. The complete C program, mycalc.c, is in Listing 6-4, and the resource
file, mycalc.rc, appears in Listing 6-5. Note that to use the C functions atoi( )
and itoa( ), we had to include the header file stdlib.h, and we put that in
mycalc.h:
#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2
HWND hlnst;
244 Peter Norton’s QuickC for Windows
HWND hWndMain;
While on the subject of buttons, we can note that you can make a button
inactive, graying out its caption and making it unresponsive to mouse clicks
with a line like this: EnableWindow(GetDlgItem(hWndDlg, 103), FALSE),
where we’re assuming that 103 is the button’s ID number (the
GetDlgItem( ) function returns a window handle to the button — that is,
buttons actually are windows). You can make it active again like this: En-
ableWindow(GetDlg!tem(hWndDlg, 103), TRUE).
G=k
File
nF Calculator 'Id
12
24
s i
36
/
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
strcpy(szAppName, "MYCALC");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
/* create application's Main window */
hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Calculator Example" /* Window's title */
WS_CAPTION ! /* Title and Min/Max */
WS_SY SMENU ! /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
W S_THIC KFRAME I /* thick sizeable frame */
WS_CLIPCHILDREN ! /* don't draw in child windows */
WS_OVERLAPPED,
CW_USEDEFAULT, 0, /* Use default X, Y */
CW_USEDEFAULT, 0, /* Use default X, Y */
NULL, /* Parent window's handle */
NULL, /* Default to Class Menu */
hlnst, /* Instance of window */
NULL); /* Create struct for WM_CREATE */
if(hWndMain == NULL)
{
(continued)
246 Peter Norton’s QuickC for Windows
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_CALCULATOR:
/* Place User Code to respond to the */
Dialog Boxes: Buttons and Textboxes 247
{
FARPROC lpfnCALCMsgProc;
lpfnCALCMsgProc = MakeProcInstance((FARPROC)CALCMsgProc, \
hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
lpfnCALCMsgProc);
FreeProcInstance(lpfnCALCMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of V\/M_COMMAND
case WM_CREATE:
SetBkMode(hDC, TRANSPARENT);
(continued)
248 Peter Norton’s QuickC for Windows
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/************************************************************************/
/* */
/* Dialog Window Procedure */
/* */
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
Dialog Boxes: Buttons and Textboxes 249
case WM_COMMAND:
switch(wParam)
{
case 101: /* Edit Control */
break;
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of CALCMsgProc */
/*
***********************************************************************/
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
int nCwRegisterClasses(void)
(continued)
250 Peter Norton’s QuickC for Windows
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/*
/************************************************************************/
POINT
RECT
RECT rParent;
int iwidth;
int iheight;
/★★★★★★★a****************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/*******************************★****************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
MYCALC MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Calculator...", IDM_F_CALCULATOR
(continued)
252 Peter Norton’s QuickC for Windows
MENUITEM SEPARATOR
MENUITEM "Exit", IDM_F_EXIT
END
END
#include "CALC.DLG"
STRINGTABLE
ft
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END
The next dialog box example will give us more experience with text boxes and
will show us how to communicate with our controls. It turns out that we can
rely on Window’s text boxes to handle most of the details of text handling for
us, and this means that we’ll be able to put together a notepad example very
easily, complete with word wrap and Cut and Paste capabilities. Let’s dig into
that now.
A Notepad Example
Start the Dialog Editor and draw a large text box, using the text box tool, as
shown in Figure 6-21. Next, give this dialog box the name Notepad in the
Caption box and click the text box twice to open the Edit Field Styles dialog
box, as shown in Figure 6-22. To create our notepad, click the Multi-line and
Vert. Scroll boxes, and then make sure that Auto HScroll is not clicked. Now
click the OK button, making our notepad multi-line and giving it a vertical
scroll bar. Add a Cancel button, setting its ID number to IDCANCEL. Finally,
add two buttons labelled Cut and Paste, as shown in Figure 6-23.
By making sure automatic horizontal scroll is off in a text box, we make sure
that automatic word wrap is turned on.
At this point, we’re ready to integrate the new notepad into a program, so
choose the Save As... item in the Dialog Editor’s File menu, saving this dialog
box as, say, notepad.dig. Next, open QuickCase:W, set up a window with a File
menu that has two items in it: Notepad... and Exit. Link the notepad in to the
Notepad... item using the Configure Link... button, and by selecting
notepad.dig. Finally, generate the new application, calling it, say, mypad.win.
Dialog Boxes: Buttons and Textboxes 253
*
EH Border I I Auto HScroll
EH Uppercase
A ab I I Lowercase EH Multi-Line
I i Password EH Vert. Scroll
["'1 c) I I No Hide Sel. I I Auto VScroll
m @ 1 I I REM Convert EH H orz. Scroll
“Basic Styles
EH Visible EH Rroup
03 T EH Djsabled EH X^b Stop
U ■ ImcJ Cancel Help
The last step is to prepare and run the program in the QuickC for Windows
environment. To do that, we’ll need to modify mypad.c, so load that in now,
as well as the mypad.mak project. QuickCase:W has generated
NOTEPADMsgProc( ) for us already, and included this for the WM_COM-
MAND part of the code that handles messages from the dialog box:
case WM_COMMAND:
switch(wParam)
{
case 101: /* Edit Control */
break;
case 102: /* Button text: "Cut" */
break;
case 103: /* Button text: "Paste" */
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
Dialog Boxes: Buttons and Textboxes 255
Now we have to make our Cut and Paste buttons active. The text box itself will
handle most of the details of text entry and will even let the user select text
(e.g., you can press the left mouse button at some location and move the
cursor to another location; when you release it, the text in between will be
highlighted). We can operate on the selected text (if any) with
SendDlgItemMessage( ) statements like these (where the text box has control
ID number 101):
These statements instruct the text box to cut (to the Windows clipboard),
copy (to the clipboard), clear, or paste (from the clipboard) the selected text.
That means that cutting and pasting is actually very simple; any time the Cut
or Paste button is clicked, we just have to send the appropriate message to the
text box which handles all the details for us (note that if no text is selected, no
text is cut or pasted over). That looks like this:
case WM_COMMAND:
switch(wParam)
{
case 101: /* Edit Control */
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
After making the Exit item in our File menu active (i.e., by copying the code
from the WM_CLOSE case), we’re ready to run. Choose the Go item in the
256 Peter Norton’s QuickC for Windows
Notepad Example a
Notepad
a
Cut [ Paste
Run menu, and our window opens. Now click on the Notepad... item in our
File menu, and the notepad appears, as in Figure 6-24. You can type in this
notepad, and Windows automatically handles all the difficult aspects of text
handling including backspaces, word wrap, and scrolling (quite an improve¬
ment over having to do it ourselves as in Chapter 3). In addition, if you select
text, you can cut it with the Cut button, and paste it back in later with the Paste
button. That’s it: We’ve created a functioning notepad program. The mypad.c
program appears in Listing 6-6, and notepad.dig, which we created with the
Dialog Editor, appears in Listing 6-7.
Before we finish with text boxes, it’s worth noticing that there is an entire
subclass of messages designed especially for them; these messages are pref¬
aced with EM (for edit message). For example, we can find the position of the
beginning and end of the selected text with EM_GETSEL (note that the first
character in a text box is character 0):
Or, we can select the start and end positions of the selected text ourselves with
EM_SETSEL and the MAKELONG( ) macro:
Or, we can find the number of lines in a multi-line text box (like ours) with
EM_GETLINECOUNT:
Overall, there are 25 text box command messages that we can send, and they
appear in Table 6-2.
TIP Note that we can even undo the last edit in a text box by sending an
EM UNDO message.
258 Peter Nortons QuickC for Windows
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
{
TranslateMessage(&msg);
DispatchMessage(&msg);
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
(continued)
260 Peter Norton’s QuickC for Windows
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM F NOTEPAD:
— »
{
FARPROC lpfnNOTEPADMsgProc;
lpfnNOTEPADMsgProc = \
MakeProcInstance((FARPROC)NOTEPADMsgProc, hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), \
hWnd, lpfnNOTEPADMsgProc);
FreeProcInstance(lpfnNOTEPADMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, IParam);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
break; /* End of WM_CREATE */
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, IParam);
}
return 0L;
} /* End of WndProc */
/************************************************************************y'
/* */
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/************************************************************************/
switch(Message)
{
(continued)
262 Peter Norton’s QuickC for Windows
case WM_INITDIALOG:
cwCenter(hWndDlg, 0) ;
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage (hWndDlg, WM_COMMAND, IDCANCEL, 0L)s ;
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: /* Edit Control */
break;
default:
return FALSE;
}
return TRUE;
} /* End of NOTEPADMsgProc */
/************************************************************************ i
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************i
Dialog Boxes: Buttons and Textboxes 263
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/* */
/************************************************************************/
GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, SrParent);
(continued)
264 Peter Norton’s Quick C for Windows
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Dialog Boxes: Buttons and Textboxes 265
That completes our coverage of buttons and text boxes as used in dialog
boxes; as you can see, there’s a great deal of utility here. In the next chapter,
we’ll see scroll bars and list boxes, two more very important (not to mention
extremely useful) Windows controls.
"
Dialog Boxes: Scroll Bars
and Listboxes
Everyone’s familiar with scroll bars and listboxes. You can find scroll bars on
the edges of most windows; they allow users to work with much more informa¬
tion in our programs than we can fit into a window at once. By moving the
small box — called the thumb— inside the scroll bar around, we can work our
way through large documents, although actually seeing only a small fraction at
a time. In fact, we’ve already used scroll bars in our programs; in the last
chapter, we set up a multi-line text box with a vertical scroll bar that let us
scroll the text in our notepad.
We’ll find in this chapter, however, that if we want to support our own scroll
bars (i.e., independent of the automatic ones associated with multi-line text
boxes), there are a lot of details that we’ll have to handle. For example, we’re
responsible for setting the position of the thumb ourselves. In this chapter,
we’ll start with a control panel application; that is, our program will have a
control panel dialog box that we can use to change the background color of
the main window simply by manipulating scroll bars.
We’ve also see listboxes throughout our exploration of QuickC for Windows.
A listbox presents us with a number of text string choices for us to select from;
clicking one of the listed choices highlights and selects that choice. Listboxes
267
268 Peter Norton's QuickC for Windows
Our goal in this example is to add a pop-up control panel to a program so that
we can customize the main window to some extent. To do that, we’ll add a
Control Panel... menu item to the File menu; when selected, this item will pop
a control panel with three scroll bars on the screen. The user can then manip¬
ulate the scroll bars to select the red, green, and blue components of the
background color of the main window. There will also be two buttons: OK and
Cancel. If the user clicks OK, we’ll change the color of the main window; if
they click Cancel, we won’t.
We might start by using the Dialog Editor to create a .dig file which we can
call, say, panel.dig. Put the name “Control Panel” in the new dialog box’s
caption bar. Click the Vertical Scroll Bar Tool in the toolbox (fifth tool down
on the right), and place three scroll bars in our dialog box, as shown in Figure
7-1 (stretch them into shape using the sizing handles).
If you’re having trouble aligning the three scroll bars — or other groups of
controls — you can use the Arrange menu in the Dialog Editor. Simply
hold down the Shift key and click the items you want to align; the last one
you click this way is the key, and the others — which now appear with
hollow sizing handles — will be aligned with respect to it. Just choose from
the options in the Arrange menu, which allow you to align the other con¬
trols with this one either vertically or horizontally.
Next, using the text tool (first tool down on the left), add some text above the
scroll bars that reads “Select New Window Color:”, and label the scroll bars
with the labels Red, Green, and Blue as shown in Figure 7-2. Finally, add the
OK and Cancel buttons, as shown in Figure 7-3, and give them the ID values
IDOK and IDCANCEL, respectively, from the dropdown ID listbox in the
upper right corner of the Dialog Editor.
Dialog Boxes: Scroll Bars and Listboxes 269
Now save everything as panel.dig; this file holds the complete specification of
our control panel, including the scroll bars and their locations like this:
LTEXT
CO
CO
The next step is to start QuickCaseiW. We can give our new application a
caption of Control Panel Example. In addition, we can add a File menu with
Dialog Boxes: Scroll Bars and Listboxes 271
Dialog Box
O Modeless
two items in it: Control Panel... and Exit. Using the Configure Link... item in
the Link To box (which appears in the Insert Menu Item dialog box), link the
Control Panel... item to panel.dig, as shown in Figure 7-4. Then save this
project as, say, mypanel.win, and generate the appropriate project files with
the Generate option in QuickCase:W’s Build menu.
At this point, our control panel program’s template is ready; all that remains
is to make the dialog box (including the scroll bars in it) active. Bring
mypanel.mak up as the current project in the QuickC for Windows develop¬
ment environment, and open mypanel.c with the Open... item in QuickC for
Windows’ File menu. As we might have expected, QuickCase:W has designed
a dialog window procedure for us named PANELMsgProc( ), and it looks like
this:
/************************************************************************ /
/* * /
/* Dialog Window Procedure */
/* * /
/* This procedure is associated with the dialog box that is included in * /
/* the function name of the procedure. It provides the service routines * /
/* for the events (messages) that occur because the end user operates /
272 Peter Norton’s QuickC for Windows
switch(Message)
&
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_COMMAND:
switch(wParam)
{
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
case WM_CLOSE:
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
default:
return FALSE;
}
return TRUE;
} /* End of PANELMsgProc */
Scroll Bars
Note that there is nothing about the scroll bars here; we’ll have to add the
appropriate message case(s) ourselves. When you use a vertical or horizontal
scroll bar, messages are generated: either WM_VSCROLL (vertical scroll bar)
or WM_HSCROLL (horizontal scroll bar). To indicate the specific action that
was taken in the vertical or horizontal scroll bar, wParam contains a scroll bar
constant like this:
Dialog Boxes: Scroll Bars and Listboxes 273
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_VSCROLL:
switch (wParam)
{
case SB_THUMBPOSITION:
//We'll add code here,
break;
274 Peter Norton’s QuickC for Windows
: default:
: break;
: }
—> break;
case WM_COMMAND:
switch(wParam)
{
case IDOK:
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, OL);
break; /* End of WM_CLOSE */
default:
return FALSE;
}
return TRUE;
} /* End of PANELMsgProc */
Now it’s time to add our code. We start by setting the range of each scroll bar
— that is, the values that the setting of the scroll bar can take. In our case, we
want each color value to range from 0 to 255, so we can use SetScrollRange( )
in the WM_INITIDIALOG case (which corresponds to the WM_CREATE case
in a standard window) like this to set the range of each scroll bar to 0-255:
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Set scrollranges
—> SetScrollRange(GetDlgltem(hWndDlg, 101), SB_CTL, 0, 255, TRUE);
—> SetScrollRange(GetDlgltem(hWndDlg, 102), SB_CTL, 0, 255, TRUE);
Dialog Boxes: Scroll Bars and Listboxes 275
Here, we use GetDlgltem( ) to get a window handle to each of the scroll bars.
To do that, we pass the handle of the dialog box itself, hWndDlg, and the ID
of the scroll bar we want to work with in it: ID numbers 101, 102, and 103 as
we’ve designed them in the Dialog Editor.
This is an important point; we saw in the last chapter that we only needed a
text box’s ID number to use with GetDlgItemText( ). Working with scroll bars
is a little harder: We’ll have to get a window handle (like hWnd) to our scroll
bars before working with them (i.e., scroll bars, like any other control, are
actually windows). When we use scroll bars, we can’t use GetDlgItemText( ) or
SetDlgItemText( ); instead, we’ll have to use standard window functions like
SendMessage( ), which take window handles instead of ID numbers.
NOTE You can also use GetDlgltem ( ) with text boxes to get window handles to
them and use SendMessage( ) to interact with them, but it’s usally much
easier to use GetDlgItemText( ) and SetDlgItemText( ).
When we set a scroll bar’s range, we first use GetDlgltem( ) to get a window
handle for the scroll bar like this:
Next, we indicate that we’re working on a scroll bar control, not a built-in
window scroll bar which appears on the side of the client area, by including
the constant SB_CTL. The other options here are SB VERT and SBHORZ.
After that, we pass the new scroll bar’s range itself, which we want to go from
0 to 255. The minimum value for a vertical scroll bar corresponds to the top,
like this:
Now that each scroll bar’s range is set up, we can start to handle our
WM_VSCROLL messages. When we get a message like this, the user has made
some change in the setting of one of our vertical scroll bars. The new setting
for the scroll bar is passed to us in the low word of IParam, and the high word
contains the scroll bar’s window handle. (Note that this is not the scroll bar’s
dialog box ID number.)
This means that we can get the scroll bar’s new position with
LO WORD (IParam), and we can even set its new position with SetScrollPos( )
because that function needs the scroll bar’s window handle, not its ID num¬
ber. The problem here is that if we only have a window handle for the scroll
bar that was changed, we don’t actually know which scroll bar it corresponds
to. In other words, we know the ID number of each scroll bar — red (101),
green (102), and blue (103) — but we have no idea what its window handle is
going to be. When we get a window handle to the scroll bar that the user
manipulated, we’ll have to work backwards to find its ID number before pro¬
ceeding (i.e., before recording the new red, green, or blue setting).
We’ve already seen that we can get a control’s window handle like this:
GetDlgItem(hWndDlg, 101). We can also go the other way, getting a control’s
ID number if we have its handle with GetWindowWord( ) like this: nID =
GetWindowWord(hWndCtl, GWW_ID), where the GWW_ID constant is a spe¬
cial constant for GetWindowWord ( ) indicating that we want the control’s ID.
And that’s it; after we get the control’s ID this way, we know which scroll bar
was changed. In other words, if wParam holds SB THUMBPOSITION, the
user has moved the thumb. We know its new setting from LOWORD (IParam),
and we now know which scroll bar was changed by using GetWindowWord ( ).
TIP The GetWindowWord ( ) function can also return the window handle of a
window’s parent, if you use the GWWJHWNDPARENT constant.
Dialog Boxes: Scroll Bars and Listboxes 277
Here we check which scroll bar was manipulated, and use SetScrollPos ( ) to
update it. To do that, we must pass these arguments to SetScrollPos ( ): the
scroll bar’s window handle (we pass GetDlgltem (hWndDlg, lOx) for clarity,
but we could have as easily passed HIWORD (lParam)), SB_CTL to indicate
that we’re working on a scroll bar control, the new setting — which we got
from LOWORD (lParam) — and a value of TRUE, which means that we want
the scroll bar redrawn (if this value is FALSE, the thumb will return to its
original position).
Now, after the user adjusts the scroll bars, we have their new values in the
integers nRed, nGreen, and nBlue. That’s it; we have the color values we want.
278 Peter Norton s QuickC for Windows
Although it was a little more difficult than working with text boxes and but¬
tons, it becomes easier with practice. At this point, then, our scroll bars are
functional, and we’ve added another Windows control to our repertoire.
The next step occurs when the user presses the OK or Cancel button, so let’s
look at that now. The Cancel button is easy; in that case, we simply close the
dialog box without making any chages to the main window. That code was
already placed in the IDCANCEL case by QuickCase:W: EndDialog(hWndDlg,
FALSE);. However, if the user clicks OK, then we have to make up a new color
for the background of the main window—that is, RGB(nRed, nGreen,
nBlue), where we’ve placed the values of the scroll bars in those integers —
and install it.
Changing the background color of the main window is a litde more difficult
than you might think. The problem is that the background color was set when
we registered the window’s class type, like this (in the code QuickCase:W
wrote for us in the function nCwRegisterClasses( )):
wndclass.hbrBackground = (HBRUSH)(C0L0R_WIND0W+1);
To change it, we’ll have to create a new solid brush of color RGB(nRed,
nGreen, nBlue) and install it as the new class brush. That’s actually possible
with the SetClassWord( ) function, as we’ll see, but the process is not easy.
First, we have to delete the current background brush with DeleteObject( ),
which functions much like SelectObject( )’s opposite. To indicate what we
want to delete, we must get the handle to the current background brush, and
to do that we need to get a window handle to the main window. Since that
window is the dialog box’s parent, we can do that like this: GetPar-
ent(hWndDlg). Next, we can get the handle to the background brush like this
with the GetClassWord( ) function: GetClassWord(GetParent(hWndDlg),
GCW_HBRBACKGROUND));. Finally, we can delete that brush with
DeleteObject( ) in the OK button’s case:
case WM_COMMAND:
switch(wParam)
{
case IDOK:
—> DeleteObject(GetClassWord(GetParent(hWndDlg), \
GCW_HBRBACKGROUND));
Now we’ve removed the main window’s background brush. The next step is to
install a new one, which we do with SetClassWord( ) and CreateSolidBrush( )
like this:
case WM_COMMAND:
switch(wParam)
{
case IDOK:
DeleteObject(GetClassWord(GetParent(hWndDlg), \
GCW_HBRBACKGROUND));
—» SetClassWord(GetParent(hWndDlg), GCW_HBRBACKGROUND, \
CreateSolidBrush(RGB(nRed, nGreen, nBlue)));
Our new brush is installed. Next, we have to make sure that Windows redraws
the main window, which we can if we invalidate it as we’ve done before, like
this:
case WM_COMMAND:
switch(wParam)
{
case IDOK:
DeleteObject(GetClassWord(GetParent(hWndDlg), \
GCW_HBRBACKGROUND));
SetClassWord(GetParent(hWndDlg), GCW_HBRBACKGROUND, \
CreateSolidBrush(RGB(nRed, nGreen, nBlue)));
—> InvalidateRect(GetParent(hWndDlg), NULL, TRUE);
At this point, we’ve changed the color of the main window. The final step in
handling the OK button is simply to close the dialog box, and we do that with
the code that QuickCase:W has already provided for the OK button case,
EndDialog(hWndDlg, TRUE):
280 Peter Norton’s QuickC for Windows
case WM_COMMAND:
switch(wParam)
{
case IDOK:
DeleteObject(GetClassWord(GetParent(hWndDlg), \
GCW__HBRBACKGROUND) ) ;
SetClassWord(GetParent(hWndDlg), GCW_HBRBACKGROUND, \
CreateSolidBrush(RGB(nRed, nGreen, nBlue)));
InvalidateRect(GetParent(hWndDlg), NULL, TRUE);
And that’s it; here is how the bulk of our function PANELMsgProc ( ) looks:
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Set scrollranges
SetScrollRange(GetDlgltem(hWndDlg, 101), SB_CTL, 0, 255, TRUE);
SetScrollRange(GetDlgltem(hWndDlg, 102), SB_CTL, 0, 255, TRUE);
SetScrollRange(GetDlgltem(hWndDlg, 103), SB_CTL, 0, 255, TRUE);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_VSCROLL:
nID = GetWindowWord(HIWORD(lParam), GWW_ID);
switch (wParam)
{
case SB_THUMBPOSITION:
// red scroll bar?
if(nID == 101){
nRed = LOWORD(lParam);
SetScrollPos(GetDlgltem(hWndDlg, 101), SB_CTL, nRed, TRUE)
}
// green scroll bar?
if(nID == 102){
nGreen = LOWORD(lParam);
SetScrollPos(GetDlgltem(hWndDlg, 102), SB_CTL, nGreen, TRUE);
}
// blue scroll bar?
if(nID == 103){
Dialog Boxes: Scroll Bars and Listboxes 281
nBlue = LOWORD(lParam);
SetScrollPos(GetDlgltem(hWndDlg, 103), SB_CTL, nBlue, TRUE)
}
break;
default:
break;
}
break;
case WM_COMMAND:
switch(wParam)
{
case IDOK:
DeleteObject(GetClassWord(GetParent(hWndDlg), \
GCW_HBRBACKGROUND));
SetClassWord(GetParent(hWndDlg), GCW_HBRBACKGROUND, \
CreateSolidBrush(RGB(nRed, nGreen, nBlue)));
InvalidateRect(GetParent(hWndDlg), NULL, TRUE);
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
default:
return FALSE;
}
return TRUE;
/* End of PANELMsgProc */
NOTE In this example, nRed, nGreen, and nBlue are static variables, so they’re set
to 0 originally; if the user doesn’t adjust the scroll bar for a particular color
component, that component will be left at 0. You can change this by setting
these values and the scroll bar settings to some default position in the
WM INITDIALOG case.
282 Peter Norton’s QuickC for Windows
FiN
Control Panel Example
Control Panel
m
Select New Window Color:
aPi a a
OK
tmm
□
a
m a a
Cancel
Congratulations: Now you’re programming scroll bars. All that’s left is to try it
out. Select the Go item in the QuickC for Windows’ development environ¬
ment Run menu, and our window appears. Bring up the control panel by
selecting the “Control Panel...” item, and our control panel appears as in
Figure 7-5. Adjust the scroll bars and click the OK button to change the main
window’s color, as in Figure 7-6. That’s it; the C code appears in Listing 7-1.
{
/***********************************************************************/
(continued)
284 Peter Norton’s QuickC for Windows
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
{
TranslateMessage(&msg);
DispatchMessage(&msg);
/* */
/* This procedure provides service routines for the Windows events */
/* (messages) that Windows sends to the window, as well as the user */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding */
/* keyboard accelerators. */
/* */
/************************************************************************/
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; V* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
Dialog Boxes: Scroll Bars and Listboxes 285
case IDM_F_CONTROLPANEL:
/* Place User Code to respond to the */
/* Menu Item Named "Control Panel..." here. */
{
FARPROC lpfnPANELMsgProc;
lpfnPANELMsgProc = MakeProcInstance((FARPROC)PANELMsgProc, \
hlnst) ;
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
lpfnPANELMsgProc);
FreeProcInstance(lpfnPANELMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
case WM CREATE:
SetBkMode(hDC, TRANSPARENT);
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, IParam);
}
return OL;
} /* End of WndProc */
/************************************************************************/
/* */
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/************************************************************************/
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Set scrollranges
SetScrollRange(GetDlgltem(hWndDlg, 101), SB_CTL, 0, 255, TRUE);
SetScrollRange(GetDlgltem(hWndDlg, 102), SB_CTL, 0, 255, TRUE);
SetScrollRange(GetDlgItem(hWndDlg, 103), SB_CTL, 0, 255, TRUE);
Dialog Boxes: Scroll Bars and Listboxes 287
case WM_VSCROLL:
nID = GetWindowWord(HIWORD(lParam), GWW_ID);
switch (wParam)
{
case SB THUMBPOSITION:
if(nID == 101){
nRed = LOWORD(lParam);
SetScrollPos(GetDlgltem(hWndDlg, 101), SB_CTL, nRed, TRUE);
if(nID == 102){
nGreen = LOWORD(lParam);
SetScrollPos(GetDlgltem(hWndDlg, 102), SB_CTL, nGreen, TRUE);
if(nID == 103){
nBlue = LOWORD(lParam);
SetScrollPos(GetDlgltem(hWndDlg, 103), SB_CTL, nBlue, TRUE);
}
break;
default:
break;
}
break;
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
case IDOK:
DeleteObject(GetClassWord(GetParent(hWndDlg), GCW_HBRBACKGROUND));
SetClassWord(GetParent(hWndDlg), GCW_HBRBACKGROUND,
CreateSolidBrush(RGB(nRed, nGreen, nBlue)));
InvalidateRect(GetParent(hWndDlg), NULL, TRUE);
EndDialog(hWndDlg, TRUE);
break;
(continued)
288 Peter Nortons QuickC for Windows
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
ft
default:
return FALSE;
}
return TRUE;
} /* End of PANELMsgProc */
/
*/
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/* */
/************************************************************************/
if(top)
pt.y = pt.y + top;
/ ************************************************************************/
/ * CwUnRegisterClasses Function */
/ *
*/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
A Database Example
Now let’s turn to list boxes. Our example here will be to put together a small
database program using a list box — and we can modify our menu-driven
phone book application to do it. Instead of adding every new item to the
menu as we did in Chapter 4, we can add items to a list box. For example, say
that we wanted to keep track of inventory like this:
Name: Apples
Number: 214
As in our phone book program, the user could type a name and number in
the main window, like this (where the user types “Apples,” presses a tab to
move to the number field, and types “214”):
Dialog Boxes: Scroll Bars and Listboxes 291
We could have an Add Item selection in our program’s menu that would add
“Apples” to a list box. After we enter all data, we could bring up the list box by
clicking another File menu item, which we might call Database..., in which
case a dialog box something like this would come up:
Database
OK
Apples
Oranges
Cancel
Bananas
Melons
We want to let the user choose an inventory item (e.g., Apples) from the list
box; when they do, we can close the dialog box and display that item’s data in
the main window like this:
In other words, this will be much like our phone book application, except that
we present our list of data items in a list box, not in a menu. Note that there
are two ways of selecting items from a list box like this:(l) by selecting (i.e.,
highlighting) an item and then clicking an OK button, or (2) by double
clicking that item in the list box. We should allow for both methods in our
program. To start, then, we need to learn about list boxes, so let’s dig into that
topic immediately.
List Boxes
Bring up the dialog editor and use the list box tool to draw a list box, stretch¬
ing it as shown in Figure 7-7, and add two buttons: OK and Cancel (give them
the IDs IDOK and IDCANCEL).
292 Peter Norton’s QuickC for Windows
Database
A ab
na OK |
®
_ j.
Cancel
- T
i*i *1
T
□ ■
Next, double click the new list box itself, bringing up all the options we see in
Figure 7-8. Note in particular the Sort, Notify, and Vert. Scroll Bar options.
Make sure that Sort is unchecked; if we check it, all entries in our list box will
automatically be sorted in alphabetical order. (It will simplify things in our
example program if we leave the entries in the order they’re inserted because
then their location in the list box corresponds to their location in our arrays
of strings.) The Notify option, which is already set, makes sure that we’re
notified when all changes are made in the list box. That’s exactly what we
want: to get messages from our list box when changes are made to it. These are
the list box notification (prefix: LBN) codes that we can get from a list box:
□
1_1 Horz. Scroll Bar |_] Owner-Draw Fixed
EH U.se Tab Stops EH Owner-Draw Variable
m
EH No Redraw □
i □
—~
1 _ .
Basic btyles
.
|
1__!_
\ L—J |
The list box notification we’re interested in is LBN_DBLCLK, which we’ll see
when a selection is clicked twice. (The other way the user makes a selection in
our example is by clicking the OK button, in which case we’ll get a message
from the OK button, not the list box). Let’s put this to work. Save the dialog
box as, say, db.dlg (for database), creating a file like this (note that the list box
has ID number 101):
Now bring up QuickCase:W and give this program a caption bar of, say,
Database Example. In a File menu, place the item Add Item, just as we did
294 Peter Norton’s QuickC for Windows
before with the phone book. Next, connect the db.dlg dialog box to an item
called Database... also in the program’s File menu, and add an Exit item as we
have before. Now generate the files with the name mydb (mydb.c, mydb.h,
and so on) and open the QuickC for Windows environment.
Notice, however, that we fill these arrays in WndProc( ), but that we’ll need
access to them when we work with the dialog box procedure (i.e., if the user
selects an entry in the list box, we want to display that data in the main window
before closing the dialog box). For that reason, we make the arrays and other
data global in this program, placing them outside any procedure’s definition:
ic^if'k'k'k'k'k'k'k'k'k'k'k'k'k'k’k'k'k'k'k'k'k'k’k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k'k
/* */
/* Main Window Procedure */
/* */
/********************************************************************** j
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
Now all our code has easy access to the data in our database. First, make the Exit
item in the File menu active by copying code from the WM CLOSE case. Next,
we can adapt code from our phone program to simply fill asNameString [ ] and
asNumberString[ ], but not to add items to the File menu (as we did in our
phone book):
/********************************************************************** /
/* *
/
/* Main Window Procedure */
/* *
/
y********************************************************************** /
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items*/
/* are processed here. */
switch (wParam)
{
case IDM_F_ADDITEM:
/* Place User Code to respond to the */
/* Menu Item Named "Add Item" here. */
nNames++;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
break;
case IDM_F_DATABASE:
/* Place User Code to respond to the */
/* Menu Item Named "Database..." here. */
{
FARPROC lpfnDBMsgProc;
lpfnDBMsgProc = MakeProcInstance((FARPROC)DBMsgProc, \
hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd,\
lpfnDBMsgProc);
FreeProcInstance(lpfnDBMsgProc);
296 Peter Norton’s QuickC for Windows
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
hMenu = GetMenu(hWnd);
hSubMenu = GetSubMenu(hMenu, 0);
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
case WM CHAR:
Dialog Boxes: Scroll Bars and Listboxes 297
Note that we simply store the items in our string arrays at this point and do not
place them into the list box here. That’s because the list box does not even
exist yet, since we have not yet created and shown the dialog box that contains
it. We can fill the list box when the dialog box is created (which it is each time
we display it) using SendMessage( ) and sending it a LB INSERTSTRING
message (LB stands for list box). That’s the way we communicate with list
boxes — by sending them messages. The messages we can send to list boxes
appears in Table 7-1.
298 Peter Norton's QuickC for Windows
y^*********************************************************************** /
/* */
/* Dialog Window Procedure */
I* / *
BOOL FAR PASCAL DBMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG
lParam)
{
HWND hWnd;
HDC hDC;
int nlndex;
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
for (nlndex = 0; nlndex < nNames; nlndex++){
—> SendMessage(GetDlgltem(hWndDlg, 101), \
—» LB_INSERTSTRING, nlndex, \
—> (LONG) (LPSTR) asNameString[nlndex]);
}
/* initialize working variables */
break; /* End of WM_INITDIALOG */
Note the two parameters we pass as wParam and lParam (the third and fourth
parameters in SendMessage( )). The wParam parameter holds an index indi¬
cating the position of the item we’re inserting (the first item is item 0, the next
item 1 and so on). The lParam parameter holds a pointer to the string we want
to insert. Note that since lParam is a LONG, however, we first have to convert
asNameString [ ], which is a near pointer in our program, to a long pointer
with LPSTR, and then to type LONG.
That’s it; at this point, our database list box appears as in Figure 7-9. We’ve been
able to fill a list box with data, simply by sending it a LB_INSERTSTRING
message. Now we want to make the list box active; when the user double clicks an
item, for example, we want to place that name (from asNameString [ ]) and the
corresponding numerical data (from asNumberString[ ]) into the main
window.
300 Peter Norton's QuickC for Windows
File
Database Example
33
Database
Apples
Oranges
Bananas OK |
Cancel
/************************************************************************ /
/* / *
/************************************************************************ /
BOOL FAR PASCAL DBMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG
lParam)
{
HWND hWnd;
HDC hDC;
int nlndex;
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
for (nlndex = 0; nlndex < nNames; nIndex++){
SendMessage(GetDlgltem(hWndDlg, 101), \
LB_INSERTSTRING, nlndex, \
(LONG) (LPSTR) asNameString[nlndex]);
}
/* initialize working variables */
break; /* End of WM_INITDIALOG */
Dialog Boxes: Scroll Bars and Listboxes 301
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: // List box
if(HIWORD(lParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, IDOK, 0L);
}
break;
We can even make the OK button appear to click itself when the user
double clicks the list box selection by using this code: SendMess¬
age (GetDlgltem(hWndDlg, IDOK), BM_SETSTATE, 1, 0L); SendMess¬
age (GetDlgltem(hWndDlg, IDOK), BM_SETSTATE, 0, 0L);.
That’s it; now double clicking an item in the list box and using the OK button
are the same thing. The next thing to do is to handle the OK button. At that
point, the user has made a selection and wants to see the corresponding data.
We can get the index (0 based) of the currently highlighted item by sending a
LB GETSEL message to our list box like this (see Table 7-1):
case IDOK:
nlndex = (int) SendMessage(GetDlgltem(hWndDlg, 101), \
LB_GETCURSEL, 0, 0L);
This returns the index of the currently highlighted item, which is zero-based,
like this:
Database
OK
index 0 Apples
index 1 oranges
Cancel
index 2 r>aiiana!s
index 3 "*■ IVlclUIIa
302 Peter Norton's QuickC for Windows
The next step is simply to use that index in our data arrays and to display the
corresponding data. Before doing that, however, let’s see how some other LB
messages work to get a little more exposure to list boxes. For example, if we
had wanted to copy the highlighted string to a string named szString, we could
have done it this way with LB GETTEXT:
Or, we could have highlighted an item (i.e., selected it) with index nlndex like
this:
We can even get the length of the current selection like this:
Back in our example program, all that remains now that we have the index
of the selected item in nlndex, is to display the correct data in the main
window. Note that since we’ve left our list box in insertion order, nlndex is
also the index of the data we want in our string arrays. We display the
requested data simply by displaying the strings asNameString [nlndex] and
asNumberString [nlndex] like this (note that we have to get a handle to the
main window with GetParent( )):
/************************************************************************ i
/* */
/* Dialog Window Procedure */
/* */
/************************************************************************ j
BOOL FAR PASCAL DBMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG
lParam)
{
HWND hWnd;
HDC hDC;
int nlndex ,-
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
Dialog Boxes: Scroll Bars and Listboxes 303
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
case IDOK:
nlndex = (int) SendMessage(GetDlgltem(hWndDlg, 101), \
LB_GETCURSEL, 0, 0L) ;
—> hWnd = GetParent(hWndDlg);
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
TextOut(hDC, 0, 0, (LPSTR) asNameString[nlndex], \
anNameStrlen[nlndex] ) ;
TextOut(hDC, 0, 2 * cyChar, (LPSTR) asNumberString[nlndex],\
anNumberStrlen[nlndex]);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
304 Peter Norton’s QuickC for Windows
default:
return FALSE;
}
return TRUE;
} /* End of DBMsgProc */
And that’s it; now our database example is fully functional. Now we’re pro¬
gramming list boxes. The C code, mydb.c, appears in Listing 7-2.
hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Database Example", /* Window's title */
WS_CAPTION 1
1 /* Title and Min/Max */
1
WS_SYSMENU 1 /* Add system menu box */
WS_MINIMIZEBOX 1
1 /* Add minimize box */
WS_MAXIMIZEBOX 1
1 /* Add maximize box */
WS_THICKFRAME 1
1 /* thick sizeable frame */
WS_CLIPCHILDREN ! /* don't draw in child windows */
WS_OVERLAPPED,
CW_USEDEFAULT, 0, /* Use default X, Y */
CW_USEDEFAULT, 0, /* Use default X, Y */
NULL, /* Parent window's handle */
NULL, /* Default to Class Menu */
hlnst, /* Instance of window */
NULL); /* Create struct for WM_CREATE */
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
CwUnRegisterClasses();
return msg.wParam;
} /* End of WinMain */
(continued)
306 Peter Norton’s QuickC for Windows
/ ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ /
/★ *
/
/ * Main Window Procedure */
/★ *
/
/ * This procedure provides service routines for the Windows events */
/ * (messages) that Windows sends to the window, as well as the user */
/ * initiated events (messages) that are generated when the user selects */
/ * the action bar and pulldown menu controls or the corresponding */
/ * keyboard accelerators. * */
/* * /
/ ************************************************************************i
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
static HMENU hMenu=0, hSubMenu; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
TEXTMETRIC tm;
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_ADDITEM:
/* Place User Code to respond to the */
/* Menu Item Named "Add Item" here. */
nNames++;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
break;
case IDM_F_DATABASE:
/* Place User Code to respond to the */
/* Menu Item Named "Database..." here. */
{
Dialog Boxes: Scroll Bars and Listboxes 307
FARPROC lpfnDBMsgProc;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, IParam);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
hMenu = GetMenu(hWnd);
hSubMenu = GetSubMenu(hMenu, 0);
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
case WM_CHAR:
if ((char) wParam == '\t'){
bEnteringNumber = TRUE;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos (0, 2 * cyChar);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
else{
hDC = GetDC(hWnd);
HideCaret(hWnd);
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/************************************************************************/
/* */
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/************************************************************************/
BOOL FAR PASCAL DBMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG
lParam)
{
HWND hWnd;
HDC hDC;
int nlndex;
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0) ;
for (nlndex = 0; nlndex < nNames; nIndex++){
SendMessage(GetDlgltem(hWndDlg, 101), \
LB_INSERTSTRING, nlndex, \
(LONG) (LPSTR) asNameString[nlndex]);
}
/* initialize working variables */
break; /* End of WM_INITDIALOG */
(continued)
310 Peter Norton’s QuickC for Windows
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101:
if(HIWORD(lParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, IDOK, 0L);
}
break;
case IDOK:
nlndex = (int) SendMessage(GetDlgltem(hWndDlg, 101), \
LB_GETCURSEL, 0, 0L);
hWnd = GetParent(hWndDlg);
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
TextOut(hDC, 0, 0, (LPSTR) asNameString[nlndex], \
anNameStrlen[nlndex]);
TextOut(hDC, 0, 2 * cyChar, (LPSTR) asNumberString[nlndex],\
anNumberStrlen[nlndex]);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of DBMsgProc */
Dialog Boxes: Scroll Bars and Listboxes 311
/************************************************************************ /
/ * *
/
/* nCwRegisterClasses Function */
/ * *
/
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/ * *
/
/************************************************************************ /
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class /
memset(&wndclass, 0x00, sizeof(WNDCLASS));
★
/* load WNDCLASS with window's characteristics /
wndclass.style = CS_HREDRAW ! CS_VREDRAW ! C S_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
★
/* Extra storage for Class and Window objects /
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hlnstance = hlnst;
wndclass.hlcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
★
/* Create brush for erasing background /
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+l);
wndclass.IpszMenuName = szAppName; /* Menu Name is App Name */
wndclass.IpszClassName = szAppName; /* Class Name is App Name */
if(!RegisterClass(&wndclass))
return -1;
return(0);
} /* End of nCwRegisterClasses * /
/************************************************************************ /
/* cwCenter Function /
/* /
/* centers a window based on the client area of its parent */
/* /
/************************************************************************ /
(continued)
312 Peter Norton’s QuickC for Windows
{
POINT pt;
RECT swp;
RECT rParent;
int iwidth;
int iheight;
/************************************************************************ /
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************ /
void CwUnRegisterClasses(void)
{
Dialog Boxes: Scroll Bars and Listboxes 313
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Combo Boxes
It’s worth mentioning that besides list boxes, we can also use combo boxes,
which are simply list boxes with an added text box. This way, the user is not
restricted to selecting items as presented in a list but can enter their own text
in the text box. We can draw combo boxes with the combo box tool in the
dialog editor’s toolbox (fourth tool down on the right). Using combo boxes,
we get many of the same kinds of messages as we’ve already seen in list boxes
(CBN stands for combo box notification):
In addition, however, we can also get these notification codes in wParam when
the text in the text box is changed:
We can handle combo boxes in the same way as we’ve handled list boxes —
with SendMessage( ). The combo box control messages available, as shown in
Table 7-2, are much like the ones we saw for the list boxes. We’ll see more
about using combo boxes in the next chapter, when we use them to save files
with.
314 Peter Norton’s QuickC for Windows
That’s it for our coverage of scroll bars and list boxes — two very important
Windows controls. Using them, we’ve been able to add considerable power to
our programs. In the next chapter, we’ll start seeing how to put all this infor¬
mation to work and see some action when we work with files.
Using Files in QuickC for
Windows
We’ ve come far already, and we’ve written many powerful programs. But we
have yet to produce anything permanent in the computer, something that will
last after we turn the machine off. Certainly, unless computers allowed us to
store data in some such permanent fashion, we’d be in trouble. Of course, we
can store data on computer disks in the form of files, and that’s what this
chapter is about.
315
316 Peter Norton’s QuickC for Windows
which means that we’ll have to put together an Open... dialog box that will
allow us to specify which file to open.
To do that, simply start up the Dialog Editor and give this dialog box the
caption Open... to indicate that it’s to open files. Now add a list box (control
ID 101) that will display the names of files available for opening, a button
marked Open (control ID 102), and a button marked Cancel (use the
dropdown ID box to give it an ID of IDCANCEL), as shown in Figure 8-1. Save
the dialog box as, say, open.dig with the Dialog Editor’s Save As... menu item.
The next step is to create the program itself. The idea here is to use our
Open... dialog box to open files and display their contents in the main win¬
dow. Start QuickCase:W and give the window a caption of, say, File Scanner.
Then add a File menu (which will actually handle files this time), giving it two
entries: Open... and Exit. Link the Open... item to the dialog box we just
created, open.dig, with the Configure Link... button. At this point, all that
remains is to generate the files necessary for our project, which we can call,
say, fopen.mak (for File Open). Generate the correct files with QuickCase:W’s
Generate option, then open fopen.c as the source file and fopen.mak as the
Using Files in QuickC for Windows 317
project file in the QuickC for Windows environment. Now we’re ready to get
to work.
To start, we can make the Exit item active as before, simply by copying the
code from the WM_CLOSE case. When the user selects the Open... item, on
the other hand, our Open... dialog box will appear. The first thing that we
might do is to fill the list box in it with the names of the files in the current
subdirectory (which is C:\QCWIN\BIN).
You might think that we have to use C functions to determine what files exist
in the current subdirectory and then add them one by one to the list box. In
fact, there is a much easier way in Windows. Because it’s very common to
display filenames like this, Windows will handle many of the details for us. All
we have to do is to send a special message to the list box, LB_DIR, and
Windows will automatically search the current subdirectories for files and
place their names in the list box for us. At present, the dialog box procedure
looks like this:
/ 'k'k'k'k'k-k-k'k'k'k k ★ **★*★**★★*★**★★**★★*★*★★★★★★★★★★**■*★★★★**★★★★★★★★★**★*■*■■*•■*•■*■■*•* /
/■ */
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/************************************************************************/
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
318 Peter Norton’s QuickC for Windows
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of OPENMsgProc */
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm);
—> cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
Now we’re ready to work with the list box, control 101, and make it display
filenames from the current subdirectory. To do that, we simply send it the
LB_DIR message and pass a pointer in IParam to the file specification we want
the displayed files to match. For example, to display all files, that specification
Using Files in QuickC for Windows 319
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg)) ;
GetTextMetrics(hDC, &tm) ;
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
Here, wParam holds the DOS file attribute of the files we want to display, like
this:
0 Normal files
1 Read-only files
2 Hidden files
4 System files
Ox 10 Subdirectories
0x20 Archive-bit set files
Note that we can display a list of subdirectories instead of plain files in our
list box by passing a value of 0x10 in wParam.
At this point, the list box is ready to go. Let’s also display the current subdirec¬
tory, including drive letter, in our dialog box. We can use the C function
getcwd( ) to get the current drive and directory, and then print it out in the
WM PAINT case like this:
case WM_PAINT:
EndPaint(hWndDlg, &ps);
break;
320 Peter Nortons QuickC for Windows
If you wanted to, you could also add a text box at this point, allowing the
user to change subdirectories (which you can do in QuickC for Windows
with the chdir( ) function).
Our dialog box is ready to appear on the screen, as in Figure 8-2. The next
step is to make it active, allowing the user to double click a file name or select
it and click the Open button. We can make our task easier by handling those
two cases together. If the user double clicks a file name in the list box (i.e.,
Windows sends us a WM_COMMAND message with wParam =101 — the list
box’s ID — and HIWORD (IParam) = LBN_DBLCLK), we can send a message
to the Open button instead, making the program function as though that
button (ID = 102) had been clicked:
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(IParam) == LBN_DBLCLK){
Now we can handle the Open button case itself. When the user clicks this
button (or double clicks the list box), they want to open the list box’s cur¬
rently selected filename. To get the current selection’s index in the list box,
Using Files in QuickC for Windows 321
we can send a LB GETCURSEL message. After we have its index, we can get
the filename itself by sending a LB_GETTEXT message like this:
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(lParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
}
break;
case 102: /* Button text: "Open” */
// Open file here.
We can use the normal C file functions in Windows, such as fread( ), fwrite( ),
fclose( ), and fseek( ). Note, however, that fopen( ) is absent from this list;
since Windows is a multi-tasking operating environment, we have to be a little
careful here — theoretically, two applications can access the same file on disk.
Windows takes care of all the details for us when we use its OpenFile( ) func¬
tion. The problem is that OpenFile( ) does not return a pointer to a FILE
structure, which is what we need in C; instead, it returns a DOS file handle.
However, we can solve that problem by passing the DOS handle to another
function, fdopen( ), which does return a pointer to a FILE structure:
You can also use the built-in Windows file functions in C: _lopen( ),
_lread( ), _lwrite( ), _lseek( ), and _lclose( ). These functions use far point¬
ers for read and write buffers — and one advantage of that is that you can
use more memory for your data.
322 Peter Norton’s QuickC for Windows
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(lParam) == LBN_DBLCLK){
LB_GETCURSEL, 0, 0L) ;
SendMessage(GetDlgltem(hWndDlg, 101), LB_GETTEXT, \
nlndex, (LONG) (LPSTR) szFileName);
—> MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
Flag Means
Note also that when we use fdopen( ) to get a file pointer (which we call fFile),
we use another flag to indicate the type of access we want (i.e., the “r” flag
here):
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(lParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
}
break;
case 102: /* Button text: "Open" */
// Open file here.
nlndex = (int) SendMessage(GetDlgltem(hWndDlg, 101), \
LB_GETCURSEL, 0, 0L) ;
SendMessage(GetDlgltem(hWndDlg, 101), LB_GETTEXT, \
nlndex, (LONG) (LPSTR) szFileName);
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
-> fFile = fdopen(MSDOSHnd, "r");
324 Peter Nortons QuickC for Windows
File-opening
Option Means
The flags you pass to fdopen( ) are simply the normal file opening flags, as in
standard C, and they appear in Table 8-2 (note that they make no provision
for sharing the file with other programs).
At this point in our program, then, our file is open and associated with the
stream we’ve called fFile. The next step is simply to read data from it line by
line. The only difference here from a standard C program is that we have to
obtain a device context to our main window first, and use TextOut( ) to do the
printing. That looks like this (where CBUFFSIZE is the size of our data buffer
that we’ve defined in fopen.h as 80):
Using Files in QuickC for Windows 325
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(lParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
}
break;
case 102: /* Button text: "Open" */
// Open file here.
nlndex = (int) SendMessage(GetDlgltem(hWndDlg, 101), \
LB_GETCURSEL, 0, 0L) ;
SendMessage(GetDlgltem(hWndDlg, 101), LB_GETTEXT, \
nlndex, (LONG) (LPSTR) szFileName);
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fFile = fdopen(MSDOSHnd, "r");
—> hDC = GetDC(GetParent(hWndDlg));
nxlndex = 0; //char position
nyIndex = 0;
while(!feof(fFile) && (nxlndex < CBUFFSIZE)){
cChar = (char) fgetc(fFile);
if(cChar == '\r'){
fgetc(fFile); //skip linefeed
—> TextOut(hDC, 0, nylndex, (LPSTR) cBuff, nxlndex);
nyIndex += cyChar;
nxlndex = 0;
}
else
cBuff[nxlndex++] = cChar;
}
nylndex + = cyChar;
TextOut(hDC, 0, nylndex, (LPSTR) cBuff, nxlndex);
ReleaseDC(GetParent(hWndDlg), hDC);
fclose(fFile);
EndDialog(hWndDlg, TRUE);
break;
Note that at the end, we close the file with fclose( ), release the device context
handle, and end the dialog. That’s it; our file scanner is now operational.
We’ve gotten the correct file name, opened the file, read from it, and printed
its contents in our main window. The result of all this work appears in Figure
8-3, where we’re reading a .dig file. Fopen.c appears in Listing 8-1, and
fopen.h in Listing 8-2.
326 Peter Norton’s QuickC for Windows
File Scanner
File
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
/* */
/* */
/* initiated events (messages) that are generated when the user selects */
(continued)
328 Peter Norton’s QuickC for Windows
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps * */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM_F_OPEN:
/* Place User Code to respond to the */
/* Menu Item Named "Open..." here. */
{
FARPROC lpfnOPENMsgProc;
lpfnOPENMsgProc = MakeProcInstance((FARPROC)OPENMsgProc, \
hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
lpfnOPENMsgProc);
FreeProcInstance(lpfnOPENMsgProc);
}
break;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
Using Files in QuickC for Windows 329
case WM_CREATE:
break; /* End of WM_CREATE */
default:
/* For any message for which you don't specifically provide a */
/ ************************************************************************/
/* */
(continued)
330 Peter Nortons QuickC for Windows
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
SendMessage(GetDlgltem(hWndDlg, 101), LB_DIR, 0, \
(LONG) (LPSTR) "*.*");
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWndDlg, &ps);
getcwd(szDriveAndDir, 30);
TextOut(hDC, 135, 4, (LPSTR) szDriveAndDir,
strlen(szDriveAndDir));
EndPaint(hWndDlg, &ps);
break;
Using Files in QuickC for Windows 331
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, OL);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(iParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, 102, OL) ;
}
break;
(continued)
332 Peter Norton’s QuickC for Windows
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of OPENMsgProc */
/ ************************************************************************/
* j
/*
/ * nCwRegisterClasses Function */
* */
/
/ * The following function registers all the classes of all the windows */
/ * associated with this application. The function returns an error code */
/ * if unsuccessful, otherwise it returns 0. */
/* *I
/ ************************************************************************/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
UsingFiles in QuickC for Windows 333
} /* End of nCwRegisterClasses */
/************************************************************************/
/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/* */
/************************************************************************/
}
(continued)
334 Peter Norton's QuickC for Windows
/************************************************************************ /
/* CwUnRegisterClasses Function */
/ * *
/
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/ * *
/
/************************************************************************ /
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
((define IDS_ERR_REGISTER_CLASS 1
((define IDS_ERR_CREATE_WINDOW 2
In addition, we already have an Open File... dialog box because we just fin¬
ished developing it for our file scanner. That dialog box looks like this in our
file open.dig:
Now we want to add this dialog box to our database program. These .dig files
are integrated into the program by including them in the program’s resource
file, which means that we’ll want to include both “open.dig” and “db.dlg” (the
dialog box that lets the user choose the record they want to examine, like
“Apples”) in the final resource file. In fact, we also want our database program
to save files, so we can add yet another dialog box (as yet undesigned), called
save.dig in the final version of mydb.rc (i.e., mydb.mak is the name we gave to
the database project):
336 Peter Norton’s QuickC for Windows
#include "MYDB.h"
MYDB MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Add Item", IDM_F_ADDITEM
MENUITEM "Open...", IDM_F_OPEN
MENUITEM "Save As...", IDM_F_SAVEAS
MENUITEM "Database...", IDM_F_DATABASE
MENUITEM SEPARATOR
MENUITEM "Exit", IDM_F_EXIT
END
END
STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class
END
The trouble, however, is that the dialog editor gives the same resource number
to all such dialog boxes. That is, the “Database” dialog box is currently
resource 100, and it will loaded as such (note the 100 at the beginning of this
line):
This is a problem because when our program creates the dialog box it needs
to use the resource number, like this (for the Database... dialog box):
case IDM_F_DATABAS E:
/* Place User Code to respond to the */
/* Menu Item Named "Database..." here. */
{
FARPROC lpfnDBMsgProc;
lpfnDBMsgProc = MakeProcInstance((FARPROC)DBMsgProc, hlnst);
One solution is to edit the file open.dig ourselves and make this dialog box,
say, resource number 200 instead:
Now when we link this to a menu item in our final program, QuickCase:W will
know that this is resource 200, and there will be no confusion (i.e., if we had
multiple dialog boxes each with the number 100, there would have been a
problem). That’s the way to use multiple dialog boxes, then: create as many
.dig files as you need with the resource editor; edit them to make sure that
each dialog box has a different resource number (or, as we’ll do in a minute,
give them different numbers when designing them); and then link to them in
QuickCase:W as usual. With that in mind, let’s design the Save As... dialog box
for our database program now.
338 Peter Norton’s QuickC for Windows
NOTE Although the Dialog Editor allows you to create multiple dialog boxes in a
single .dig file (and gives them different resource numbers automatically),
QuickCase:W only allows a maximum of one dialog box per .dig file.
To design our Save As... dialog box, bring up the dialog editor now and give
this new dialog box the name Save As... in its caption. We should let the user
either select an existing file or type a new filename here, so that means that
we’ll use a combo box. Select the combo box tool (fourth tool down on the
left in the toolbox) and stretch a combo box (giving it ID number 101) to
cover the left half of the dialog box. Next, add two buttons: Save (ID 102) and
Cancel (set its ID number to IDCANCEL as before), as shown in Figure 8-4.
Now give this dialog box the ID number 300 instead of 100 by clicking the
dialog box itself and editing the ID number which appears in the upper right
corner, changing it to 300 as shown in Figure 8-5. (Note that we could have
done the same thing for open.dig — read it back into the dialog editor and
changing its number there instead of editing the .dig file itself). Finally, save
this dialog box as, say, save.dig.
Using Files in QuickC for Windows 339
If we examine save.dig, this is what we find (note that this is now resource
300):
At this point, we can update the database program from the last chapter,
adding our two new dialog boxes. To do that, start QuickCase:W and add two
new menu items, Open... and Save As..., to the mydb.mak project, as shown in
Figure 8-6, and simply link those menu items to the correct dialog boxes
(Open... to open.dig, and Save As... to save.dig). Then use the Update option
to update the program files (which we’ve already named mydb.c, mydb.rc, and
so on).
340 Peter Norton’s QuickC for Windows
File Edit
QuickCase:W - (MYDB.WIN)
Design Build lools Options Help
0
FF TT1 «» Database Example 33
Add Item
i
Open...
Save As...
Database...
Exit
«»
Now open mydb.c in the development environment and you’ll find that there
are three dialog box procedures: DBMsgProc( ) (for the Database... item),
OPENMsgProc( ) (for Open...), and SAVEMsgProc( ) (for the Save As...
item). We will simply borrow the code for OPENMsgProc( ) from the dialog
box procedure, of the same name, that we have already developed in this
chapter; the procedure we’ve got to work on now is SAVEMsgProc( ). So far,
it looks like this:
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
Using Files in QuickC for Windows 341
case WM_COMMAND:
switch(wParam){
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of SAVEMsgProc */
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
342 Peter Norton’s QuickC for Windows
In addition, we can print the current working directory in our dialog box as
before, with getcwd( ) and TextOut( ) in the WM_PAINT case:
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
SendMessage(GetDlgItem(hWndDlg, 101), CB_DIR, 0, (LONG) \
(LPSTR) "*.DAT");
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWndDlg, &ps);
EndPaint(hWndDlg, &ps);
break;
Now our dialog box will appear as shown in Figure 8-7; note that the combo
box holds the names of the .dat files from the current directory.
Using Files in QuickC for Windows 343
■= Database Example LJ
File
Save As...
C:\QCWIN\BIN
test.dat
test2.dat Save
test3.dat
test4.dat
Cancel
What remains is to add the code that will make this dialog box active and get
it (i.e., the buttons) working. There will be three ways for the user to indicate
what file they want to save data in: (1) double clicking a filename in the combo
box’s list box, (2) selecting a name in the combo box’s list box and clicking
the Save button; and (3) typing a new name in the combo box’s text box and
clicking the Save button.
It turns out that handling all of these cases is not as hard as you might think.
In fact, all we’ll really have to do is to read the filename out of the combo
boxes text box when the user clicks Save (assuming we send a message to that
button in the CBN_DBLCLICK case). That’s because when the user clicks or
double clicks a filename in the combo boxes list box, that filename is dis¬
played in the combo boxes text box, and we can read it from there.
First, let’s handle the double click case as though the Save button (ID number
102) were clicked. Here, we can simply send the correct message to the Save
button like this:
{
static char szDriveAndDir[30];
static int cyChar;
PAINTSTRUCT ps;
HWND hWnd;
344 Peter Norton’s QuickC for Windows
HDC hDC;
TEXTMETRIC tm;
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
SendMessage(GetDlgltem(hWndDlg, 101), CB_DIR, 0, (LONG) \
(LPSTR) "*.DAT");
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWndDlg, &ps) ;
getcwd(szDriveAndDir, 30);
TextOut(hDC, 120, 4, (LPSTR) szDriveAndDir, strlen(szDriveAndDir));
EndPaint(hWndDlg, &ps);
break;
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam){
Now we have to design the code that will handle the Save button itself. In all
of the above cases, the filename we’re supposed to use is currently being
displayed in the combo box’s text box, so we can simply use
GetDlgItemText( ) to read the filename directly from that text box:
UsingFiles in QiiickCfor Windows 345
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
break;
case 102: /* Button text: "Save" */
// Save file here.
And that’s it; now we have the name of the file we’re supposed to open and
save data to. We can open it using OpenFile( ) as before, except this time,
we’ll use an OF_CREATE flag instead:
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
break;
case 102: /* Button text: "Save" */
// Save file here.
GetDlgltemText(hWndDlg, 101, (LPSTR) szFileName, 30);
Using this flag makes OpenFile ( ) create a new file if no file with the indicated
file name exists, or open and truncate an existing file to zero length. Note that
since the user might have typed the filename in, there is always a possibility of
error here; if OpenFile ( ) could not open the file or create it, it returns a value
of -1, which we can check and, if necessary, display a message box:
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
break;
case 102: /* Button text: "Save" */
// Save file here.
GetDlgltemText(hWndDlg, 101, (LPSTR) szFileName, 30);
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_CREATE);
346 Peter Norton1s QuickC for Windows
—> break;
Note that we executed a break statement after displaying the message box so
that we would leave the wParam = 102 case (i.e., the Save button) without
executing the EndDialog( ) statement that would normally close the dialog
box:
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
break;
case 102: /* Button text: "Save" */
// Save file here.
GetDlgltemText(hWndDlg, 101, (LPSTR) szFileName, 30);
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_CREATE);
if(MSDOSHnd == -1){ //error
MessageBox(hWndDlg, (LPSTR) "File Error", \
(LPSTR) "Error", MB_ICONEXCLAMATION ! MB_OK);
—> break;
}
—> break;
This way, the dialog box stays on the screen and the user is free to select
another file to save data to. When the user clicks another choice or button, the
appropriate message is sent to SAVEMsgProc ( ).
Next in our program (assuming there was no error) we can convert the MS-
DOS file handle we got from OpenFile ( ) into a pointer to a stream — that we
might call fFile — with fdopen( ). That looks like this (in an actual applica¬
tion, of course, this statement should be checked for errors as well):
Using Files in QuickC for Windows 347
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L) ;
break;
case 102: /* Button text: "Save" */
// Save file here.
GetDlgltemText(hWndDlg, 101, (LPSTR) szFileName, 30);
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_CREATE);
if(MSDOSHnd == -1){ //error
MessageBox(hWndDlg, (LPSTR) "File Error", \
(LPSTR) "Error", MB_ICONEXCLAMATION ! MB_OK);
break;
}
fFile = fdopen(MSDOSHnd, "w");
Now we’re ready to write data. The actual process of writing is really the same
as under standard C, as we’ll see. In our database program, you might recall
that we stored our data in various arrays; for example, if we had 214 apples, we
would have stored that information in, say, record 0 of our arrays like this:
asNameString[ ] [ ] asNumberString[ ] [ ]
‘Oranges”
“Bananas”
anNumberStrlen [
This gave us four separate arrays: asNameString[] [] for the product’s name;
anNameStrlen [] for the length of each name; asNumberString[] [] for the
quantity of the product (stored as a string); and anNumberStrlen[] for the
length of each quantity string. That’s a lot of arrays. In C, it’s usually easier to
348 Peter Norton’s QuickC for Windows
write and read data if it’s divided up into discrete records, rather than in many
arrays. For that reason, we can take the database’s data and simply place it into
an array of records named mydata[], each element of which is defined like
this:
struct myentry{
char NameString[20]; //Product's name
char NumberString[20]; //Quantity <of product
int NameStrlen; //Length of name string
int NumberStrlen; //Length of quantity string
} mydata[10];
This allows us to store our data in mydata [ ] like this, record by record:
mydata [9]
This is much easier as far as storing our data record by record goes. Now the
name of, say, item 0 will be in mydatafO].NameString[], and the quantity of
that item will be in mydata [0] .NumberString []. This means that we only have
to write the data out to disk as 10 records (as opposed to trying to manage all
four original arrays), forming a file like this:
Using Files in QuickC for Windows 349
mydata[0]
mydata[l]
mydata[2]
mydata[3]
mydata[4]
mydata[5]
mydata[6]
mydata[7]
my data [8]
my data [9]
To write our data, then, we simply use fwrite( ) as in standard C, writing out all
10 records, and then close the file:
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
break;
case 102: /* Button text: "Save" */
// Save file here.
GetDlgltemText(hWndDlg, 101, (LPSTR) szFileName, 30);
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_CREATE);
if(MSDOSHnd == —1){ //error
MessageBox(hWndDlg, (LPSTR) "File Error", \
(LPSTR) "Error", MB_ICONEXCLAMATION ! MB_OK);
break;
}
fFile = fdopen(MSDOSHnd, "w");
-> }
—> fclose(fFile);
If you’re ever worried about overwriting an existing file with fwrite( ), you
can use the GetTempFile( ) function to create unique files.
350 Peter Norton’s QuickC for Windows
Finally, we blank the main window by painting over it and redrawing it, as
we’ve done before:
case WM_COMMAND:
switch(wParam){
case 101: /* Combo Box */
if(HIWORD(lParam) == CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L) ;
break;
case 102: /* Button text: "Save" */
»
}
fFile = fdopen(MSDOSHnd, "w");
for(nIndex = 0; nlndex < nNames; nlndex++){
fwrite(&mydata[nlndex], sizeof(struct myentry), 1, fFile);
}
fclose(fFile);
—> break;
That’s all for writing our data. Similarly, we can read the data in again by
modifying the fread( ) statement in the Open... dialog box to this (allowing us
to read in all data records):
while(Ifeof(fFile) ) {
And that’s it; now our database program can read and write files. We can place
data in it and store it on disk. For example, if we had a quantity of apples,
Using Files in QuickC for Windows 351
oranges, and bananas, we could store all that data in our database, save it in a
file named, say, fruit.dat, and then read it back in again, as shown in Figure
8-8. Congratulations: Now you’re writing database programs in Windows. The
source code for our program, mydb.c, appears in Listing 8-3.
Incidentally, before reading in data from a file, you might want to allocate
some memory space for it. As we’ll see in Chapter 10, this is not a simple matter
of using malloc( ). In a Windows program, you can allocate memory on the
local (near) heap with LocalAlloc( ), get a pointer to it with LocalLock( ), then
release it later with LocalUnlock( ), and free the memory with LocalFree( ).
To do the same on the global (far) heap, you would use GlobalAlloc( ),
GlobalLock( ), GlobalUnlock( ), and GlobalFree( ). Note also that although
this sounds as though you have to lock the allocated memory in place, it is only
truly locked in Windows Real mode.
{
(continued)
352 Peter Nortons QuickC for Windows
/***********************************************************************/
strcpy(szAppName, "MYDB");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
if(hWndMain == NULL)
{
UsingFiles in QuickC for Windows 353
/************************************************************************/
/* */
/* Main Window Procedure */
/* */
/* This procedure provides service routines for the Windows events */
/* (messages) that Windows sends to the window, as well as the user */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding */
/* keyboard accelerators. */
/* */
/************************************************************************/
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
static HMENU hMenu=0, hSubMenu; /* handle for the menu */
switch (Message)
case WM COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here,
switch (wParam)
case IDM_F_ADDITEM:
/* Place User Code to respond to the */
case IDM_F_OPEN:
/* Place User Code to respond to the */
/* Menu Item Named "Open..." here. */
{
FARPROC lpfnOPENMsgProc;
lpfnOPENMsgProc = MakeProcInstance((FARPROC)OPENMsgProc, \
hlnst);'
nRc = DialogBox(hlnst, MAKEINTRESOURCE(200), hWnd, \
lpfnOPENMsgProc);
FreeProcInstance(lpfnOPENMsgProc);
}
break;
case IDM_F_SAVEAS:
/* Place User Code to respond to the */
/* Menu Item Named "Save As..." here. */
Using Files in QuickC for Windows 355
{
FARPROC lpfnSAVEMsgProc;
lpfnSAVEMsgProc = MakeProcInstance((FARPROC)SAVEMsgProc, \
hlnst);
nRc = DialogBox(hlnst, MAKEINTRESOURCE(300), hWnd, \
lpfnSAVEMsgProc);
FreeProcInstance(lpfnSAVEMsgProc);
}
break;
case IDM_F_DATABASE:
/* Place User Code to respond to the */
/* Menu Item Named "Database..." here. */
{
FARPROC lpfnDBMsgProc;
case IDM_F_EXIT:
/* Place User Code to respond to the */
/* Menu Item Named "Exit" here. */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
break; /* End of WM_COMMAND */
case WM_CREATE:
hMenu = GetMenu(hWnd);
hSubMenu = GetSubMenu(hMenu, 0);
hDC = GetDC(hWnd);
GetTextMetries(hDC, &tm);
(continued)
356 Peter Norton’s QuickC for Windows
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;
&
case WM_CHAR:
if ((char) wParam == '\t'){
bEnteringNumber = TRUE;
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 2 * cyChar);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
else{
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
mydata[nNames].Numberstring, \
mydata[nNames].NumberStrlen), 2 * cyChar);
}
else{ /* Name case */
mydata[nNames].Namestring[mydata[nNames].NameStrlen] = \
(char) wParam;
mydata[nNames].NameStrlen++;
TextOut(hDC, 0, 0, (LPSTR) mydata[nNames].Namestring, \
mydata[nNames].NameStrlen);
SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
mydata[nNames].NameString, mydata[nNames].NameStrlen), \
0) ;
}
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
break;
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/ /
/' */
/* Dialog Window Procedure */
/* */
/* This procedure is associated with the dialog box that is included in */
/* the function name of the procedure. It provides the service routines */
/* for the events (messages) that occur because the end user operates */
/* one of the dialog box's buttons, entry fields, or controls. */
/* */
/************************************************************************/
(continued)
358 Peter Norton’s QuickC for Windows
BOOL FAR PASCAL DBMsgProc(HWND hWndDlg, WORD Message, WORD wParam, LONG
lParam)
{
HWND hWnd;
HDC hDC;
int nlndex;
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
for (nlndex = 0; nlndex < nNames; nIndex++){
SendMessage(GetDlgltem(hWndDlg, 101), \
LB._INSERTSTRING, nlndex, \
(LONG) (LPSTR) mydata[nlndex].Namestring);
}
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM__CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101:
if(HIWORD(lParam) == LBN_DBLCLK){
SendMessage(hWndDlg, WM_COMMAND, IDOK, 0L);
}
break;
case IDOK:
nlndex = (int) SendMessage(GetDlgltem(hWndDlg, 101), \
LB_GETCURSEL, 0, 0L);
hWnd = GetParent(hWndDlg);
hDC = GetDC(hWnd);
HideCaret(hWnd);
SetCaretPos(0, 0); /* Next, clear text */
InvalidateRect(hWnd, NULL, 1);
SendMessage(hWnd, WM_PAINT, 0, 0L);
TextOut(hDC, 0, 0, (LPSTR) mydata[nlndex].NameString, \
mydata[nlndex].NameStrlen);
TextOut(hDC, 0, 2 * cyChar, (LPSTR) \
mydata[nlndex].Numberstring,\
UsingFiles in QuickC for Windows 359
mydata[nlndex].NumberStrlen);
ShowCaret(hWnd);
bEnteringNumber = FALSE;
ReleaseDC(hWnd, hDC);
EndDialog(hWndDlg, TRUE);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of DBMsgProc */
/************************************************************************ /
/* / *
/************************************************************************ /
(continued)
360 Peter Norton’s QuickC for Windows
TEXTMETRIC tm;
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
SendMessage(GetDlgltem(hWndDlg, 101), LB_DIR, 0, (LONG) \
(LPSTR) "*.DAT");
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWndDlg, &ps);
getcwd(szDriveAndDir, 30);
TextOut(hDC, 135, 4, (LPSTR) szDriveAndDir, strlen(szDriveAndDir));
EndPaint(hWndDlg, &ps);
break;
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, 0L);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
if(HIWORD(lParam) == LBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, 0L);
break;
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
default:
return FALSE;
}
return TRUE;
} /* End of OPENMsgProc
/* */
*/
(continued)
362 Peter Norton’s QuickC for Windows
switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Change directory here as required
hDC = GetDC(GetParent(hWndDlg));
GetTextMetrics(hDC, &tm) ;
cyChar = tm.tmHeight;
ReleaseDC(GetParent(hWndDlg), hDC);
SendMessage(GetDlgltem(hWndDlg, 101), CB_DIR, 0, (LONG) \
(LPSTR) "*.DAT");
/* initialize working variables */
break; /* End of WM_INITDIALOG */
case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWndDlg, &ps);
getcwd(szDriveAndDir, 30);
TextOut(hDC, 120, 4, (LPSTR) szDriveAndDir, strlen(szDriveAndDir));
EndPaint(hWndDlg, &ps) ;
break;
Using Files in QuickC for Windows 363
case WM_CLOSE:
/* Closing the Dialog behaves the same as Cancel */
PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, OL);
break; /* End of WM_CLOSE */
case WM_COMMAND:
switch(wParam){
case IDCANCEL:
/* Ignore data values entered into the controls */
/* and dismiss the dialog window returning FALSE */
EndDialog(hWndDlg, FALSE);
break;
(continued)
364 Peter Norton’s QuickC for Windows
}
break; /* End of WM_COMMAND */
default:
return FALSE;
}
return TRUE;
} /* End of SAVEMsgProc */
/ * ********************************************************************* ** I
/* */
/ nCwRegisterClasses Function
*
*/
/★ */
/ * The following function registers all the classes of all the windows */
*
/ associated with this application. The function returns an error code */
\
/ * if unsuccessful, otherwise it returns 0. */
/* */
* ********************************************************************* ** /
/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************i
/* cwCenter Function */
Using Files in QuickC for Windows 365
/ * *
/
/* centers a window based on the client area of its parent * /
/* -k
/
/ ************************************************************************ /
HWND hWnd;
int top;
{
POINT pt;
RECT swp ;
RECT rParent;
int iwidth;
int iheight;
GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, &rParent);
if(top)
/★★★★★★★★★★■a-************************************************************* /
/* CwUnRegisterClasses Function * /
/* /
/* Deletes any refrences to windows resources created for this * /
(continued)
366 Peter Nortons QuickC for Windoivs
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
That’s all for our coverage of file handling; as you can see, there is quite a
variety of powerful options here. Now, however, let’s continue our tour of
QuickC for Windows by looking into some fast data-handling techniques in
the next chapter.
Fast Data Handling
One common complaint about Windows is that it can be very slow; that is,
there is so much graphics and disk handling going on that program perfor¬
mance suffers. For that reason, this chapter is about some ways to save time
that we as C programmers can use in our Windows programs. In particular,
we’ll look at some common methods of speeding up data handling, such as
sorting pointers to records instead of sorting the records themselves, or per¬
forming ordered searches. This kind of efficient data handling can help Win¬
dows programs considerably, and the techniques and tips that follow are
popular among Windows programmers. We’ll start by seeing how using point¬
ers instead of arrays can help us save time and improve program performance.
367
368 Peter Norton’s QuickC for Windows
And, of course, we can refer to individual elements of this array with an index
which we might call i:
—> array[i] = 7;
In other words, we can substitute * (array + i) for array [i] in most places in our
programs. In the example we’ve just seen, that would produce this:
—> *(array + i) = 7;
At first, substituting * (array + i) for array [i] might seem like nothing more
than an exercise in making programs more complex and difficult to read; it
turns out, however, that there really is a good deal more here, both in terms
of execution speed and memory-storage efficiency.
Here, we’re simply assigning each character in the string “Hello, world\n.” to
the char variable mychar, one character at a time. Each time through, we
increment the index i and assign the character to mychar. However, each time
we make the reference to array[i], the program is forced to find the location
in memory where that element is stored like this:
&array[0] + i*sizeof(char)
In other words, C starts at the base address of the array (&array[0]) finds the
offset of the desired element into the array (i * sizeof(char)), and adds the two
to get the element’s address. In this simple case, sizeof(char) is just one byte
and the multiplication is easy — but if we had an array of floats or doubles, it
would have been different. Each time we reference an item in an array, a
multiplication is performed, and multiplications are among the most time-
consuming instructions in the 80 x 86. On the other hand, consider a change
to the program like this:
array_pointer
H e 1 1 o W o r 1 d • \n
Every time through the loop, array_pointer points to the next element:
array_pointer
H e 1 1 o W o r 1 d . Nri
array[0][ ] H e 1 1 o 0
array[l] [ ] W o r 1 d . 0
array[2] [ ] M a r s h m a 1 1 o w 0
array[3][ ] F 1 i n g i n g 0
array[4] [ ] r e d 0
array[5][ ] b u t t o n s 0
array[6][ ] w i 1 d 1 y 0
You can see that much space is wasted — the array has to have as many col¬
umns as there are characters in the longest string. With an array of pointers
instead, however, we can do this:
Fast Data Handling 371
array [1] [ ] -* W o r 1 d • 0
array [2] [ ] -► M a r s h m a 1 1 o w 0
array[3] [ ]-► F 1 i n g i n g 0
array [4] [ ] -+ r e d 0
array[5] [ ] — b u t t o n s 0
array [6] [ ] -* w i 1 d 1 y 0
my_pointers[0] my_data[0]
pointer A key
big_array[ ]
my_pointers[l] my_data[l]
pointer B key
big_array[ ]
my_pointers[2]
pointer C 1 key
big_array[ ]
372 Peter Nortons QuickC for Windows
We can refer to the value of the field named key in record 0 as: my_point-
ers[0]->key, and the value there is 3. The corresponding field in record 1 is
my_pointers[l]->key, which holds 2, and so on:
my_pointers[0]->key: 3
my_pointers[l]->key: 2
my_pointers[2]->key: 1
Now let’s say that we want to sort the records on the value in key. We can do
that just by moving the pointers around (which keep pointing the the same
structure as they did before), like this:
my_pointers[0] my_data[0]
-*- big_array[ ]
my_pointers[l] my_data [ 1 ]
pointer B 2 +- key
•*- big_array[ ]
my_pointers[2] my_data[2]
big_array[ ]
my_pointers[0]->key: 1
my_pointers[l]->key: 2
my_pointers[2]->key: 3
when we have a lot of data, and it can save you much time in your Windows
programs. Let’s see how this looks in code with an example; to do that, how¬
ever, we have to learn how to sort data, so let’s take a look at how the shell sort
works.
8 7 6 5 4 3 2 1
To sort this list into ascending order, divide it into two partitions like this:
8 7 6 5 4 3 2 1
Then compare the first element of the first partition with the first element of
the second:
1 I
8 7 6 5 4 3 2 1
I_I I_l
NOTE It is this switching of elements, much like a shell game, that gives the shell
sort its name.
4 3 2 1 8 7 6 5
374 Peter Norton’s QuickC for Windows
While this is somewhat better than before, we’re still not done. The next step
is to divide each partition itself into two partitions, and repeat the process,
comparing 4 with 2 and 8 with 6:
l I l i
4 3 2 1 8 7 6 5
Again, we switch the second set of two pairs, leaving us with this:
2 1 4 3 6 5 8 7
This looks even closer. Now the partition size is down to one element, which
means that this is the last time we’ll need to sort the list. We need to compare
the first, and only, element in each partition with the first, and only, element
in the next partition. Here that means that we compare elements 2, 4, 6, and
8 with elements 1, 3, 5, and 7. When we swap them all, we get:
1 2 3 4 5 6 7 8
And that is how the standard shell sort works — at least if there’s an even
number of items to sort (in which case breaking them up into balanced
partitions is easy). The case where we have an odd number of elements is
slightly more difficult. For example, if we had a list of nine elements to sort, we
would start by breaking them up into two partitions like this (note that there
is no last element in the second partition):
987654321x
I_I I_I
In this case, we just don’t perform any comparison (i.e., there is no value in
the x position that might have to be placed earlier in the array); instead, we
just continue on to the next smaller partition size. We keep going as before,
working until the partition size becomes 1, perform the final switches, and
then we’re done.
Fast Data Handling 375
Let’s see this in an example where we sort the pointers to an array of struc¬
tures. We start off by setting up the data in the array my_data[ ] in our window
procedure:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT1 ps; /* holds PAINT information */
int nRc=0; /* return code */
struct large_data_struct{
int key;
int big_array[20];
} my_data[10]; <—
Next, we’ll need a pointer to each structure in that array. In addition, we can
load the descending values 10, 9, 8 ... 1 in the key fields at the same time and
print them out in the WM_PAINT case like this (where we set cyChar, the
height of the current font, in the WM CREATE case as before):
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc = 0; /* return code */
TEXTMETRIC tm;
static int cxChar, cyChar;
char szOutString [ 40];
struct large_data_struct{
int key;
int big_array[20];
} my_data[10];
struct *my_pointers[10 ] ;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
376 Peter Norton’s QuickC for Windows
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM__CREATE */
cYloc = 0;
for(i =0; i < 10; i++){
my_data[i].key = 10 - i;
my_pointers[i] = &my_data[i];
sprintf(szOutString, "my_pointers[%d]->key: %d", \
i, my_pointers[i]->key);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;
}
So far, our program has indicated that the key fields of the records are in
exactly reverse order, like this:
my—Pointers[0]->key: 10
my—Pointers[1]->key: 9
my_pointers[2]->key: 8
Fast Data Handling 377
my_pointers[3]->key: 7
my—Pointers[4]->key: 6
my—pointers[5]->key: 5
my_pointers[6]->key: 4
my_pointers[7]->key: 3
my_pointers[8]->key: 2
my—pointers[9]->key: 1
Now we can sort the records by moving the pointers in the array my point¬
ers [ ] around. For example, when we’re done, the pointer now in my_point-
ers[0] will be exchanged with the one in my_pointers[9], so we’ll get this:
my—pointers[0]->key: 1
my_pointers[1]->key: 2
my_pointers[2]->key: 3
my_pointers[3]->key: 4
my_pointers[4]->key: 5
my_pointers[5]->key: 6
my_pointers[6]->key: 7
my pointers[7]->key: 8
my_pointers[8]->key: 9
my_pointers[9]->key: 10
Let’s start developing the sorting part of the program. First, we have to deter¬
mine the partition size and set up the loop to loop over different partition
sizes. In our case, that looks like this:
10 9876 54321
And we can do that this way, where we keep halving the partition size (note
that our sorting code needs to know how many elements there are to sort,
which you can find with sizeof( ) if you need to):
do {
partition—size = (partition_size +1) / 2;
Next, we’ll need to know the number of partitions so we can loop over each
one, comparing the elements in it to the elements of the next partition. That
number is not just number_items / partition_size because number_items may
378 Peter Norton’s QuickC for Windows
do {
partition_size = (partition_size +1) / 2;
—> number_partitions = number_items / partition_size;
—> if (number__items%partition_size) number_partitions++;
do {
partition_size = (partition_size +1) / 2;
number_partitions = number_items / partition_size;
if (number_items%partition_size) number_partitions++;
—> for(i = 1; i < number_partitions; i + +){
}
}while(partition_size > 1);
Because the current partition may be anywhere in the array, let’s get the index
of the first and last elements of the partition, and call them first_index and
last_index:
-first_index
j-last_index
10 9876 54321
partition_size = 5-*> I_I I___I
That way, we can work from first_index to last_index, comparing the element
at first_index to the one at first_index + partition_size, and so on up to
lastindex:
do {
partition_size = (partition_size +1) / 2;
number_partitions = number_items / partition_size;
Fast Data Handling 379
if (number_items%partition_size) number_partitions++;
first_index = 0;
for(i =1; i < number_partitions; i + +){
last_index = first_index + partition_size;
if(last_index > number_items - partition_size)
last_index = number_items - partition_size;
for(j = first_index; j < last_index; j++){
}
}
first_index + = partition_size;
}
}while(partition_size > 1);
Here’s where the real comparison is done. We have the location of the first
element, j:
j
I
10 9876 54321
partition_size = 5-► I_I I_I
j j + partition_size
I I
10 9876 54321
partition_size = 5 I_I I_I
do {
partition_size = (partition_size +1) / 2;
number_partitions = number_items / partition_size;
if (number_items%partition_size) number_partitions++;
first_index = 0;
for(i =1; i < number_partitions; i++){
last_index = first_index + partition_size;
if(last_index > number__items - partition_size)
380 Peter Norton’s QuickC for Windows
In this way, we compare each element in the current partition to the one in
the next, then continue with the next partition, and so on. Then we divide the
partition sizes in half and repeat the process, continuing until we’re down to
partition sizes of one. And that’s it; our pointer-sorting program is done. This
is how our WndProc( ) procedure looks:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUC11 ps; /* holds 'PAINT information */
int nRc = 0; /* return code */
TEXTMETRIC tm;
static int cxChar, cyChar;
char szOutString[40];
struct large_data_struct{
int key;
int big_array[20];
} my_data[10];
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break;
Fast Data Handling 381
cYloc = 0;
for(i =0; i < 10; i++){
my—data[i].key = 10 - i;
my pointers[i1 = &my_data[i];
sprintf(szOutString, "my_pointers[%d]->key: %d", i,\
mY_pointers[i]->key);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;
}
cYloc + = cyChar;
do {
partition_size = (partition_size +1) / 2;
number__partit ions = number_items / partition_size;
if (number_items%partition_size) number_partitions++;
first_index = 0;
for(i =1; i < number_partitions; i++){
last_index = first_index + partition_size;
if(last_index > number_items - partition_size)
last_index = number_items - partition_size;
for(j = first_index; j < last_index; j++){
if(my_pointers[j]->key > my_pointers[j+partition_size]->key){
temp = my_pointers[j+partition_size];/* swap pointers *
my_pointers[j+partition_size] = my_pointers[j];
mY_pointers[j] = temp;
}
}
first_index += partition_size;
}
}while(partition_size > 1);
382 Peter Nortons QuickC for Windows
cYloc = 0;
cXloc = 30 * cxChar;
for(i =0; i < 10; i++){
sprintf(szOutString, "my_pointers[%d]->key: %d",\
i, my_pointers[i]->key);
TextOut(hDC, cXloc, cYloc, (LPSTR) szOutString, \
strlen(szOutString)) ;
cYloc += cyChar;
}
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
The whole C program appears in Listing 9-1, and the resultant window ap¬
pears in Figure 9-1. As you can see, we were able to change the apparent
ordering of our data simply by sorting the pointers to that data.
my_pointers[01>key: 10 my_pointersI01->key: 1
my pointersil ]->key: 9 my_pointers[1]->key: 2
my_pointers[2]->key: 8 my_pointers[2]->key: 3
my_pointers[3j->key: 7 my_pointers[3]->key: 4
my pointers[4]->key: 6 my_pointers[4]->key: 5
my_pointers[5]->key: 5 my_pointers[5]->key: 6
my_pointers[6]->key: 4 my_pointers[6J->key: 7
my_pointers[7]->key: 3 my_pointers[7]->key: 8
my_pointers[8]->key: 2 my_ pointers[8]->key: 9
my pointers[9]->key: 1 my_pointers[9]->key: 10
Sorting...
strcpy(szAppName, "SORT");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString,
sizeof(szString));
return nRc;
}
}
nWndunits = GetDialogBaseUnits();
nWndx = LOWORD(nWndunits);
nWndy = HIWORD(nWndunits);
nX = ( (0 * nWndx) / 4) ;
nY = ((1 * nWndy) / 8);
(continued)
384 Peter Norton’s QuickC for Windows
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION) ;
return IDS_ERR_CREATE_WINDOW;
}
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding */
/* keyboard accelerators. */
/* */
/************************************************************************/
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc = 0; /* return code */
TEXTMETRIC tm;
static int cxChar, cyChar;
char szOutString[40];
struct large_data_struct{
int key;
int big_array[20];
} my_dat a[10];
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break;
(continued)
386 Peter Norton’s QuickC for Windows
cYloc = 0;
for(i =0; i < 10; i++){
my_data[i].key = 10 - i;
mY_pointers[i] = &my_data[i];
sprintf(szOutString, "my_pointers[%d]->key: %d", i,\
mY—Pointers[i]->key);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;
}
cYloc = 0;
cXloc = 30 * cxChar;
for(i = 0; i < 10; i++){
Fast Data Handling 387
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
/************************************************************************/
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
(continued)
388 Peter Nortons QuickC for Windows
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hlnstance = hlnst;
wndclass.hlcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
/* Create brush for erasing background */
wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+l);
wndclass.IpszMenuName = szAppName; /* Menu Name is App Name */
wndclass.IpszClassName = szAppName; /* Class Name is App Name */
if(!RegisterClass(&wndclass))
return -1;
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************/
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
There are more topics to be covered while we’re discussing data referencing
with pointers, and they have to do with data organization. One of these topics
is linked lists, which is a very popular programming construction that depends
on the use of pointers. Let’s explore that next.
Linked Lists
A linked list is an ideal way of storing data if you don’t know in advance how
many data items you’ll have to store. It works like this: For each data item,
Fast Data Handling 389
there is also a pointer pointing to the next data item. At any time, you can add
another data item to the list, as long as you update the last pointer to point to
the new data item. If you start at the beginning of the list, you can work your
way up by using the pointer in each item that points to the next item. The last
pointer in the series is usually a NULL pointer (so you know the list is done
when you reach it).
Note in particular that the last pointer is a NULL pointer. Here’s what the
structure type that makes up the elements of a linked list might look like:
struct mylist{
char msg[20] ;
struct mylist *ptr;
}
(Note that in QuickC for Windows it’s legal to declare ptr as a pointer to the
same type of structure that it’s defined in — that is, as a pointer to structure
type my list.) We can use that structure definition and set up a linked list with
three elements, itemO, iteml, and item2:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC ; /* handle for the display device */
PAINTSTRUCT’ ps; /* holds PAINT information */
int nRc = 0; /* return code */
TEXTMETRIC tm;
390 Peter Norton’s QuickC for Windozus
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
—> cYloc = 0;
: sprintf(szOutString, "%s", itemO.msg);
: TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
: cYloc += cyChar;
: pointer_to_next_item = itemO.ptr;
: sprintf(szOutString, "%s", pointer_to_next_item->msg);
: TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString) ) ;
: cYloc += cyChar;
Fast Data Handling 391
pointer_to_next_item = pointer_to_next_item->ptr;
sprintf(szOutString, "%s", pointer_to_next_item->msg);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString) ) ;
cYloc += cyChar;
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
In this program, we work our way up the list by using the pointer in the
present item, which points to the next item. In that way, the data items are
chained together. The way to move up this chain is indicated in this line,
where we update pointer_to_the_next_item to point to the one after that:
pointer_to_next_itern = pointer_to_next_item->ptr;
This way, we can move up the list, giving the result shown in Figure 9-2.
Circular Buffers
There is another type of linked list that is also popular — a list where the last
item points to the first one so the whole thing forms a circle. This is called a
circular or ring buffer. The most well-known circular buffer in your machine
is the keyboard buffer.
What happens there is that while one part of the operating system is putting
key codes into the keyboard buffer, another part is taking them out. The
location in the buffer where they are put in is called the tail of the buffer, and
the location where the next key code is read from is called the head.
When keys are typed in, the tail advances. When they are read, the head does.
As you write to and read from the keyboard buffer, the head and tail march
around. Note that any location in the circular buffer can be the head or the
tail. When the buffer is filled, the tail comes up behind the head, and the
buffer-full warning beeps.
Use circular buffers when some part of your program is reading data and
some part is writing it — but at different rates. Store the location of the head
and tail, and after you put data into the buffer, advance the tail. After you take
data out, advance the head. This way, you can use the same memory space for
reading and writing (as long as you keep taking out the data you put in and
the buffer doesn’t fill up).
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc = 0; /* return code */
int nArray[10], loop_index;
char szOutString[20];
Fast Data Handling 393
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
default:
394 Peter Norton’s QuickC for Windows
We just keep scanning up the list of values until we find what we’re looking
for. On the other hand, we can be more intelligent wheji searching a sorted
list. For example, if our sorted array had these values in it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
and we were searching for the entry with 10 in it, we could start off in the
center of the list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Since 10 is greater than 8, we divide the upper half of the array in two and
check the mid point again:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
The value we’re looking for, 10, is less than 12, so we move down and cut the
remaining distance in half:
i-1-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
And in this way, we’ve zeroed in on our number, cutting down the number of
values we have to check. Let’s see how this looks in a program. First, we set up
our array. In this example, let’s search an array of 10 elements for the entry
with 8 in it:
—> SearchValue = 8;
—> strcpy(szOutString, "Searching the ordered list for the value 8.");
—> TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen(szOutString));
Now we cut the array into two partitions and check the test value which is right
between them, at position Testlndex:
Then we need to start searching. We will keep looping over partition size — if
the partition becomes 0 without any success, then the value we’re looking for
isn’t in the array:
-» do {
—> Partition = (int) (Partition + 1) / 2;
Let’s first check to see if we’ve found our value — and if so, we can quit:
But if the search value is smaller, on the other hand, we want to move to the
lower partition (which holds lower values):
—> else
—> Testlndex = Testlndex - Partition;
}while(Partition > 0);
And that’s almost all there is; we just keep going until we find what we’re
looking for, or the partition size becomes 0, in which case it’s not there.
If we were unsuccessful, however, there are two last remaining tests that we
should apply: We should check the value we’re searching for against the very
first and last entries in the array. That is, our algorithm demands that all
numbers that it checks be straddled by two other values, and that’s true of every
element in the array except for the first and last ones. That means that if we
didn’t find what we were looking for, we have to check these last two values
explicitly:
SearchValue = 8;
strcpy(szOutString, "Searching the ordered list for the value 8.");
TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen(szOutString));
cYloc = cyChar;
Partition = 5; //Start at half of array size
Testlndex = Partition;
do{
Partition = (int) (Partition + 1) / 2;
if(nArray[Test Index] == SearchValue){
sprintf(szOutString, "Value of %d in element %d", \
SearchValue, Testlndex);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, \
strlen(szOutString));
break;
}
if(nArray[Testlndex] < SearchValue)
Testlndex = Testlndex + Partition;
else
Testlndex = Testlndex - Partition;
}while(Partition > 0);
// Can only find straddled numbers, so add these tests:
if(nArray[9] == SearchValue){
sprintf(szOutString, "Value of %d in element 9", SearchValue)
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, \
strlen(szOutString));
—> }
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
And we’re done with the ordered search. The entire program appears in
E
Listing 9-2, and the resulting window appears in Figure 9-3.
Search
earching the ordered list for the value 8.
alue of 8 in element 8
strcpy(szAppName, "SEARCH");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
{
/* registering one of the windows failed */
Loadstring(hlnst, IDS_ERR_REGISTER_CLASS, szString, sizeof(szString)) ;
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}
if(hWndMain =® NULL)
{
Loadstring (hlnst, IDS_ERR__CREATE__WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
TEXTMETRIC tm;
int nRc=0; /* return code */
char szOutString[80];
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
SearchValue = 8;
strcpy(szOutString, "Searching the ordered list for the value 8.");
TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen(szOutString));
cYloc - cyChar;
do{
Partition = (int) (Partition + 1) / 2;
if(nArray[Testlndex] == SearchValue){
sprintf(szOutString, "Value of %d in element %d", \
SearchValue, Testlndex);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString,\
strlen(szOutString));
break;
Fast Data Handling 403
}
if(nArray[TestIndex] < SearchValue)
Testlndex = Testlndex + Partition;
else
Testlndex = Testlndex - Partition;
}while(Partition > 0);
if(nArray[0] == SearchValue){
sprintf(szOutString, "Value of %d in element 0", SearchValue);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, \
strlen(szOutString));
}
if(nArray[9] == SearchValue){
sprintf(szOutString, "Value of %d in element 9", SearchValue);
TextOut(hDC, 0, cYloc, (LPSTR) szOutString, \
strlen(szOutString));
}
default:
/* For any message for which you don't specifically provide a */
}
return 0L;
} /* End of WndProc */
/****************************** ******************************************/
/* */
/* nCwRegisterClasses Function */
/* */
(continued)
404 Peter Norton’s QuickC for Windows
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************/
int nCwRegisterClasses(void)
{ a
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************,
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Fast Data Handling 405
... - .
■
Debugging QuickC for
Windows Programs
This is our debugging chapter. Even for the best programmers, errors still
occur. This problem becomes more severe as programs become larger (a
common thing in Windows programs) — the number of errors can increase,
and they can become harder to find. Debugging becomes a fact of life for
programmers, and because it is, it is a topic we should cover.
This is a test.\n
407
408 Peter Norton’s QuickC for Windows
In code, we open the file, allocate some memory space for it, read the first
letter of the file in, and then display it in our window. Memory handling itself
is an entire topic in Windows. The usual steps are (1) get a memory handle
with LocalAlloc( ); (2) convert the handle into a pointer — which is what the
C functions require — with LocalLock( ) (which locks the memory block in
place in real mode, but not in protected mode); (3) when done, unlock the
memory with LocalUnlock( ); and (4) free it with LocalFree( ). This process
looks like this:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
int MSDOSHnd;
OFSTRUCT of;
HANDLE hLocal; <—
char *pBuffer; <—
FILE * fptr;
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
fread(pBuffer, 1, 1, fptr);
—> LocalFree(hLocal);
fclose(fptr);
If you want to use more memory than what’s available on the local heap, use
GlobalAlloc( ), GlobalLock( ), GlobalUnlock( ) and GlobalFree( ) to allo¬
cate and free memory on the global heap. And note that GlobalLock( )
returns a far pointer, not a near one.
When we run it, however, the program’s window appears, but nothing appears
in it. What’s wrong? We could start by adding assertions:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
int MSDOSHnd;
OFSTRUCT of;
HANDLE hLocal;
char *pBuffer;
FILE * fptr;
switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */
These new lines, assert(fptr != NULL) and assert(buffer != NULL), check the
value of our pointers fptr and buffer. If there is something wrong with our
assertions, the assertion macro ends the program and places a message box on
the screen. The program then exits with an error code (-1). We can run this
new program; when we do, we see the message box that appears in Figure
10-1.
ASSERTION FAILED
ioniJff
iwiiiniiwiiMnii.
Assertion Example
T
As you can see, it appears that fptr was being left NULL. The assertion tells us
what assertion failed (fptr != NULL), in what file (C:\QCWIN\BIN\ASSERT.C)
and in what line (114). This makes debugging easier — you can fill your
program with assertions and never see them unless there is an abnormal
condition.
For example, we now know that we can’t open file.dat. If we look at the
OpenFile( ) call, it’s easy to see why: There was a error; we had been trying to
open files.dat, not file.dat. After we fix that, the program works properly; that
is, it prints out the first character in the file file.dat, as shown in Figure 10-2.
QuickC for Windows also has a much more powerful debugging tool — the
interactive debugging environment’s own debugger. This debugger allows us
to work on the program in one window and the program’s source code in
another, and we’ll spend the rest of the chapter debugging with it.
Interactive Debugging
The Debug menu in QuickC for Windows appears in Figure 10-3; using these
commands, we’ll be able to trace through our programs line by line, searching
for errors.
For example, let’s say that we used the file named file.dat, as before, which
contains these characters:
This is a test.Xn
412 Peter Norton’s QuickC for Windows
Debug
Calls...
Breakpoints...
Quickwatch... Shift+F9
Watch Expression...
Modify Variable... Ctrl+F9
Hex Mode Alt+F9
Now let’s say that we wrote a program to open this file, allocate memory space,
read the file in, print the contents out, as well as to count and display the
number of ‘e’s in it. Our first attempt at the window procedure might look like
this:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
static int cyChar;
TEXTMETRIC tm;
FILE * fptr;
int i, number, cYloc, MSDOSHnd;
float answer;
HANDLE hLocal;
char *buffer;
static char szOutString[20], szFileName[] = "file.dat";
OFSTRUCT of;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
/* End of WndProc */
414 Peter Norton’s QuickC for Windows
Debug Example
niAih"
Number of ‘e's: 785
However, when we run it, some random characters are printed in the first line
of the window where we expect the text, “This is a test.\n”, and the number of
‘e’s found in this file is displayed as 785, as shown in Figure 10-4. Clearly,
there’s a problem. It’s time to debug.
To do that, open the Project... menu item in the QuickC for Windows Options
menu, displaying the Project dialog box as shown in Figure 10-5. Make sure
that the Build Mode is set to Debug, as in Figure 10-5. This will allow us to
debug our code.
When your program is fully debugged, however, select the Release option
for the Build Mode instead. This means that debugging information will be
omitted from the final .exe file.
Project
Breakpoints
Now we’re ready to debug. The fundamental idea of debugging is to deter¬
mine what the program is doing at any particular stage of its execution, find
errors, and correct them. To do that, we will set a breakpoint to halt execution
at a particular place in our program. In QuickC for Windows, there are four
main types of breakpoints, and they stop program execution in these cases:
At this point, we might want to examine, say, the two pointers we have in our
program, buffer (a pointer to our character buffer) and fptr (our file
pointer), to make sure that that part of the code functioned properly. To do
that, we move to the above line in our code and select the Breakpoints... item
416 Peter Norton’s QuickC for Windows
Breakpoints
Break: at Location Q1 OK |
: "-rV'fh-
—w! :Srl-
Breakpoints:
Add
Delete
Oear At
in QuickC for Windows’ Debug menu. This opens the Breakpoints dialog box,
as shown in Figure 10-6.
The top list box currently says: “at location,” which means that we’re setting a
breakpoint at a certain location. There are other options here (which you can
see by clicking the down arrow on this list box), and here they are:
at Location
at Location if Expr is True
at Location if Expr has Changed
if Expr is True
if Expr has changed
at Wnd Proc
at Wnd Proc if Expr is True
at Wnd Proc if Expr has Changed
at Wnd Proc if Message is Received
This is the dialog box that allows us to specify which of these types of break¬
points we want to set (a program may have many breakpoints, all of which may
be of different types). For example, if we indicated that we want our break¬
point to be triggered by a certain expression (e.g., the “at Location if Expr is
True” option), we have to enter our expression in the Expression text box
lower in the Breakpoint dialog box. Expressions like these can be valid C
Debugging QuickC for Windows Programs 417
Being able to stop program execution like this when an expression has
changed is extremely valuable. If one of your variables is being filled with
an incorrect value at some point in your program, you can now find that
point simply by watching when the variable’s value changes.
You might notice the line number in the Breakpoints dialog box, indicating
where we are at present in the program. That location, line 127, corresponds
to the beginning of the for loop in our program (i.e., the location of the caret
when we opened the Breakpoints dialog box):
To set this line as a breakpoint, click the Add button in the Breakpoints dialog
box. When you do, this breakpoint is added to the list of breakpoints in the
Breakpoints list box as “|,errors.c,).127”, as shown in Figure 10-7 (where er-
rors.c is the name of our program). You can maintain and manipulate the
breakpoints in your program with the aid of this list box; that is, you can set
breakpoint after breakpoint, adding them to this box one by one. Later, you
can delete them by selecting them and clicking the Delete button, or you can
remove all breakpoints by clicking the Clear All button.
418 Peter Norton’s QuickC for Windows
Breakpoints
Break: al Location OK |
Breakpoints:
127"
(Add] |
Clear All
As a shortcut, you can also toggle breakpoints on and off at certain code lines
with the F9 key. That is, you can place the caret at a certain line, press F9, and
QuickC for Windows will set a breakpoint there. Pressing F9 again when the
caret is at the same location toggles the breakpoint off. (The same action is
performed by clicking the button with a small hand icon in it which appears
in the upper right corner of the QuickC for Windows integrated environ¬
ment.) Click the OK button in the Breakpoints dialog box now; a red stripe
appears at line 127 in our code, as shown in Figure 10-8.
<1> C:\QCWIN\BIN\ERRORS.C
i ++)
szOutString[i]=buf fer[l];
if(buffer[i] == 'e') number++;
>
TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen{
cYloc += cyChar;
JT
MSDOSHnd = OpenFil
f dopen (MSDOSHnd.. 11
fread(buffer, 1, 2
fclose(fptr);
szOutString
if(buf fer[i
Now when we run our program, execution will stop when we reach this line of
code, and we’ll find ourselves back in the integrated development environ¬
ment. Let’s give it a try. Simply select the Go item in the Run menu; program
execution begins and continues until we reach line 127 of our window proce¬
dure. At that breakpoint, we stop and can see the code once again, as in Figure
10-9.
At this point, we can see both our code and the window we’ve created; in this
way, we can see both where we are in the program and what code brought us
here. In particular, we’ve already executed this code:
buffer = LocalLock(hLocal):
fdopen(MSDOSHnd, "r");
—> fclose(fptr);
for(i = 1; i < 20; i++){
420 Peter Norton’s QuickC for Windows
And we should have allocated a data buffer, opened the file file.dat, and read
it in. Let’s check to see if the contents of file.dat (“This is a test.Xn”) have
actually arrived in memory at the location pointer to by our char pointer
buffer. To do that, we can set up a watch window by selecting the Watch
Expression... item in the Debug menu.
When we do, the Watch Expression dialog box appears. Type “buffer” and
click the Add button, as shown in Figure 10-10, adding buffer to our list of
current expressions to watch. Now click OK, and the dialog box disappears,
leaving a new window on the screen, which is labeled <2> Watch. As we’ve
seen, the number in angle brackets, 2, is QuickC for Windows’ way of indicat¬
ing that this is our second window (and you can move between windows
rapidly simply by clicking them or by selecting them in the QuickC for Win¬
dows Window menu).
Currently, the value of the pointer buffer is displayed in the Watch window. In
fact, for char arrays, Debug lists as much of the character string as it can, and
we see that it certainly does not contain the string “This is a test.Xn.” In other
words, we’re now able to determine that our file was not read in.
File Edit
QuickC for Windows [break] - ERRORS.EXE
View Project Bun Debug Tools Options Window Help
Ld
m - rn 1.1 .1^1=::! rr-TT-i rjg
Watch Expression
In general, watch windows are very useful to observe what happens to cer¬
tain variables in a program as the program executes. As we’ll see soon, we
can step through a program line by line, and as we do, we might want to
watch a whole series of variables in a watch window to see how they change
in different parts of our code. If you’re not entirely sure what’s going on in
your program, this can be invaluable.
This means that we have to go back over the code where the file is read in.
When we do, we notice that the first use of the pointer fptr (the file pointer)
is in this line, where we read the data in:
In other words, fptr was never assigned a value. We can change that by assign¬
ing the return value from fdopen ( ), which it should have gotten from the
beginning. Our new window procedure looks like this:
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
static int cyChar;
TEXTMETRIC tm;
FILE * fptr;
int i, number, cYloc, MSDOSHnd;
float answer;
HANDLE hLocal;
char *buffer;
static char szOutString[20], szFileName[] = "file.dat";
OFSTRUCT of;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
Debugging QuickC for Windows Programs 423
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
LocalUnlock(hLocal);
LocalFree(hLocal);
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return OL;
} /* End of WndProc */
Now we can try our program again. To stop the current debugging session,
choose the Stop Debugging item in the Run menu. Next, start the program
again by selecting Go in the Run menu. QuickC for Windows will rebuild the
program (because we’ve changed the code) and run it again. (Another way of
doing the same thing is to select the Restart item in the Run menu.)
Again the program executes until we come to our breakpoint. The watch
window shows us what’s in our buffer again automatically, and we see that the
text of our file has been read in, as shown in Figure 10-12. Now remove the
breakpoint at line 127 (either with the Delete button in the Breakpoint dialog
box, or by positioning the caret at that line and pressing F9), and select the Go
item in the Run menu. The program then runs, and we see the new results in
Figure 10-13.
<2> Watch
+buffer = OxlBAD:OxlADO "This is a test\r\n"
This is better than before, but we’re still getting an incorrect number of ‘e’s,
and the first letter of our text is incorrect (the small upright bars at the end of
the text are the <crxlf> pair). We know that the data buffer is being filled
with the correct data because we’ve seen it in the watch window. Still, our
output string, which we’ve named szOutString, is not being filled with all of
those characters. To see what’s wrong, let’s watch as each character is moved
into that string, one by one.
End the program (with the Stop Debugging item in the Run menu, or the
Close item in our program’s system menu) and replace the breakpoint at line
127, the beginning of the for loop. That loop is constructed to fill szOutString
like this:
fclose(fptr);
Now select Go from the Run menu, and our program runs, stopping at the
breakpoint. Our first step is to look at szOutString in the Quickwatch window,
and we do that as before, by placing the caret over szOutString and using the
Quickwatch... item in the Debug menu (or the eyeglasses button, or Shift-F9).
As shown in Figure 10-14, szOutString does not, as yet, have anything that the
debugger can interpret as a character string.
We can now execute our code line by line. There are two options for doing
that, the Trace Into and Step Over commands. Trace Into, which you can find
in the Run menu (or simply by pressing F8, or by clicking the button with the
dotted wavy arrow to the right of the eyeglasses button), executes each line of
code it comes to. Step Over, which you can find in the Run menu (or by
pressing F10, or by clicking the button with the footprint icon in the upper
right corner of the QuickC for Windows window), does the same thing —
File Edit
QuickC for Windows [break] - ERRQRS.EXE
View Project Run Debug Tools Options Window
7B
Help
i Ah 3JL jlI iffT.L--i--.L-l iTffl/: .1 r.
Quickwatch
Subject: Zoom
[0] = 0
[1] = 0
3 Add Watch
Modify...
[21 = 0
[3] = 0 Cancel
[41 = 0
[5] = 0
Help
cYloc += cyChar;
except that it steps over function calls, which means that you don’t have to
step through them. In other words, if you reach a TextOut( ) call, you should
use Step Over rather than Trace Into. The code in TextOut( ) is executed, but
we don’t work through every line of it ourselves.
Press F8 or FI 0 a few times now, executing the body of the if loop a few times
(the current line in the code is highlighted in yellow), and take another look
at szOutString. As we can see in Figure 10-15, all but the first character is filled.
If we look at this line in the code, we see that we’ve started the loop index off
at 1 instead of 0 (a common problem in C) :
fclose(fptr);
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
static int cyChar;
TEXTMETRIC tm;
FILE * fptr;
int i, number, cYloc, MSDOSHnd;
float answer;
HANDLE hLocal;
char *buffec¬
static char szOutString[20], szFileName[] = "file.dat";
OFSTRUCT of;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
LocalUnlock(hLocal);
LocalFree(hLocal);
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
430 Peter Norton’s QuickC for Windows
Now when we step through the same if loop, we can see the characters being
inserted into szOutString correctly, as in Figure 10-16. At this point, our string
is coming out correctly, but we still have problems counting the number of
‘e’s.
We keep track of the number of ‘e’s in the variable named number. And,
while stopped at our breakpoint, we can examine the value in number. This
time, let’s watch it in the Watch window. Add that variable to the Watch
window, as in Figure 10-17, where number holds the value 3,165.
Now single step through the if loop (by continually pressing F8 or F10, or by
moving the caret below the for loop and selecting the Continue to Cursor
item in the Run menu), continuing on to the line where we print out the
number of ‘e’s we’ve found:
buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
fptr = fread(buffer, 1, 20, fptr);
fclose(fptr);
for(i =0; i < 20; i++){
szOutString[i]=buffer[i]; //fill szOutString
if(buffer[i] == 'e') number++; //count number of 'e's
}
TextOut(hDC, cYloc, 0, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;
At that point, however, number has the value 3,166, as we can see in the watch
window in Figure 10-18. The problem is clear; we forgot to initialize our
variable number (another common C problem). In fact, looking through our
code, we can also see that we didn’t initialize cYloc, the y location of our text
output. We can initialize those values correctly like this:
432 Peter Norton’s QuickC for Windows
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
—> number = 0;
for(i =0; i < 20; i++){
szOutString[i]=buffer[i]; //fill szOutString
if(buffer[i] == 'e') number++; //count number of 'e's
}
cYloc = 0;
TextOut(hDC, cYloc, 0, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;
LocalUnlock(hLocal);
LocalFree(hLocal);
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
default:
434 Peter Norton’s QuickC for Windows
}
return OL;
} /* End of WndProc */
In fact, if we had wanted to, we could have modified the value in our
variable number while the program was running by using the Modify Vari¬
able... item in the Debug menu.
[i];
number++;
cYloc = 0;
TextOut(hDC, 0, 0, (LPSTR) szOutString, strler
cYloc += cyChar;
Sit
00128 026
/***********************************************************************/
(continued)
436 Peter Norton’s QuickC for Windows
if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}
LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)
{
HMENU hMenu=0; /* handle for the menu */
HBITMAP hBitmap=0; /* handle for bitmaps */
HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
static int cyChar;
TEXTMETRIC tm;
FILE *fptr;
int i, number, cYloc, MSDOSHnd;
float answer;
HANDLE hLocal;
char * buff ec¬
static char szOutString[20], szFileName[] = file.dat";
Debugging QuickC for Windows Programs 437
OFSTRUCT of;
switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */
number = 0;
for(i = 0; i < 20; i++){
szOutString[i]=buffer[i];
if(buffer[i] == 'e') number++;
}
cYloc = 0;
TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;.
(continued)
438 Peter Norton’s QuickC for Windows
LocalUnlock(hLocal) ;
LocalFree(hLocal) ;
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps) ;
break; /* End of WM_PAINT * */
default:
/* For any message for which you don't specifically provide a */
/* service routine, you should return the message to Windows */
/* for default message processing. */
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0L;
} /* End of WndProc */
/************************************************************************/
/* */
/* nCwRegisterClasses Function */
/* */
/* The following function registers all the classes of all the windows */
/* associated with this application. The function returns an error code */
/* if unsuccessful, otherwise it returns 0. */
/* */
/************************************************************************/
int nCwRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
return(0);
} /* End of nCwRegisterClasses */
/************************************************************************/
/* CwUnRegisterClasses Function */
/* */
/* Deletes any references to windows resources created for this */
/* application, frees memory, deletes instance, handles and does */
/* clean up prior to exiting the window */
/* */
/************************************************************************ j
void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));
UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
That’s all for our coverage of debugging in the QuickC for Windows environ¬
ment — and that’s all for our book. We’ve come far in our coverage of QuickC
for Windows: from the basics all the way up to Open and Save As dialog boxes,
and from placing elementary strings on the screen up to creating our own
paint program.
However, there’s much more to learn about Windows; we cannot cover every¬
thing in a book like this, which is primarily about QuickC for Windows. The
next step is to gain more experience in programming in the Windows environ¬
ment (either through books, by programming, or both). It takes time to gain
enough experience to become a professional Windows programmer, but the
results are usually well worth the investment of time, and you’ve got a good leg
up now with the tools and techniques we’ve covered in QuickC for Windows.
Good luck (and happy programming).
V , N •
'
441
442 Peter Norton’s QuickC for Windows
It is important that you register your purchase of any Simon & Schuster software package.
By completing and returning your Owner Registration Card, you become eligible for:
Software Support
S & S will provide support to registered owners. Our technical support number is (212) 373-
7212 or FAX (212) 373-8192.
We realize that our software packages are put to a wide variety of uses, however, we can
only answer questions about the software package itself. We cannot support the hardware
and operating system required to run our software packages.
Before calling our Technical Support Department, please make sure you have followed the
steps in the "Pre-call Checklist" below.
Pre-call Checklist
1. If you are having difficulty understanding the program, have you read and performed
the suggestions listed in the manual?
2. If you are not sure how to operate the program, have you used the help system (where
available) to find the answer?
3. If there seems to be a problem in the software, can you reproduce the problem by
following your steps again?
4. If the program displayed an error message, please write down the exact message.
5. You should be familiar with the hardware configuration you are using. We may need to
know the brand/model of your computer, printer, the total amount of memory avail¬
able, what video adaptor(s) you have in the system, the operating system version, etc.
6. When you call our Technical Support Department, please be at your computer or be
prepared to repeat the sequence of steps leading up to the problem.
Important! Read before Opening Sealed Diskette
END USER LICENSE AGREEMENT
The software in this package is provided to You on the condition that You agree with SIMON & SCHUSTER, INC. ("S&S") to the terms and
conditions set forth below. Read this End User Agreement carefully. You will be bound by the terms of this agreement if you open the
sealed diskette. If You do not agree to the terms contained in this End User License Agreement, return the entire product, along with your
receipt, to Brady, Simon & Schuster, Inc., 15 Columbus Circle,! 4th floor, New York, NY 10023, Attn: Refunds, and your purchase price
will be refunded.
S&S grants, and You hereby accept, a personal, nonexclusive license to use this software program and associated documentation in this
package, or any part of it ("Licensed Product"), subject to the following terms and conditions:
1. License
The license granted to You hereunder authorizes You to use the Licensed Product on any single computer system. A separate license,
pursuant to a separate End User License Agreement, is required for any other computer system on which You intend to use the Licensed
Product.
2. Term
This End User License Agreement is effective from the date of purchase by You of the Licensed Product and shall remain in force until
terminated. You may terminate this End User License at any time by destroying the Licensed Product together with all copies in any form
made by You or received by You. Your right to use or copy the Licensed Product will terminate if You fail to comply with any of the terms
or conditions of this End User License Agreement. Upon such termination, You shall destroy the copies of the Licensed Product in your
possession.
3. Restriction against Transfer
This End User License Agreement, and the Licensed Product, may not be assigned, sublicensed, or otherwise transferred by You to another
party unless the other party agrees to accept the terms and conditions of the End User License Agreement. If You transfer the Licensed
Product, You must at the same time either transfer all copies, whether in printed or machine-readable form, to the same party or destroy
any copies not transferred.
4. Restrictions against Copying or Modifying the Licensed Product
The Licensed Product is copyrighted and except for certain limited uses as noted on the copyright page, may not be further copied without
the prior written approval of S&S. You may make one copy for backup purposes provided You reproduce and include the complete
copyright notice on the backup copy. Any unauthorized coyping is in violation of this Agreement and may also constitute a violation of
the United States Copyright Law for which You could be liable in a civil or criminal lawsuit. You may not use, transfer, copy, or otherwise
reproduce the Licensed Product, or any part of it, except as expressly permitted in this End User License Agreement.
5. Protection and Security
You shall take all reasonable steps to safeguard the Licensed Product and to ensure that no unauthorized person shall have access to it and
that no unauthorized copy of any part of it in any form shall be made.
6. Limited Warranty
If You are the original consumer purchaser of a diskette and it is found to be defective in materials or workmanship (which shall not include
problems relating to the nature or operation of the Licensed Product) under norma I use, S&S wil I replace it free of charge (or, at S&S's option,
refund your purchase price) within 30 days following the date of purchase. Following the 30-day period, and up to one year ofter purchase,
S&S will replace any such defective diskette upon payment of a $5 charge (or, at S&S's option, refund your purchase price), provided that
the Limited Warranty Registration Card has been filed within 30 days following the date of purchase. Any request for replacement of a
defective diskette must be accompanied by the original defective diskette and proof of date of purchase and purchase price. S&S shall have
no obligation to replace a diskette (or refund your purchase price based on claims of defects in the nature or operation of the Licensed
Product.
The software program is provided "as is" without warranty of any kind, either expressed or implied, including but not limited to the
implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the program
is with You. Should the program prove defective, You (and not S&S) assume the entire cost of necessary servicing, repair or correction.
Some states do not allow the exclusion of implied waraanties, so the above exclusion may not apply to You. This warrant gives you
specific legal rights, and You may also have other rights which vary from state to satate.
S&S does not warrant that the functions contained in the program will meet your requirements or that the operation of the program
will be uninterrupted or error free. Neither S&S nor anyone else who has been involved in the creation of production of this product
shall be liable for any direct, indirect, incidental, special, or consequential damages, whether arising out of the use or inability to use
the product, or any breach of a warranty, and S&S shall have no responsibility except to replace the diskette pursuant to this limited
warranty (or, at its option, provide a refund of the purchase price).
No sales personnel or other representative of any party involved in the distribution of the Licensed Product is authorized by S&S to make
any warranties with respect to the diskette or the Licensed Product beyond those contained in this Agreement. Oral statements do not
constitute warranties, shall not be relied upon by You, and are not part of this Agreement. The entire agreement between S&S and You
is embodied in this Agreement.
7. General
If any provision of this End User License Agreement is determined to be invalid under any applicable statute of rule of law, it shall be deemed
omitted and the remaining provisions shall continue in full force and effect. This End User License Agreement is to be governed by the
construed in accordance with the laws of the State of New York.
Simon & Schuster, Inc. and the authors make no warranties, express or implied in connection with the software, and expressly exclude
all warranties of fitness for a particular purpose. Simon & Schuster, Inc. and the authors shall have no liability for consequental, incidental,
or exemplary damages.
QuickC for Windows
REPLACEMENT ORDER FORM
Please use this form when ordering a 3.5-inch disk or a replacement for a defective diskette.
CITY_STATE_ZIP_
COMPUTER BRAND & MODEL _ DOS VERSION_MEMORY_K
70-19571
Get the Spark. Get BradyLine.
Published quarterly. Free exclusively to our customers.
I I Check here to begin your subscription.
QuickC for Windows
Please fill out the information below and return it to the address listed with your original 5.25-inch
diskette. Please print clearly.
I am ordering a replacement diskette after 30 days but within one year. I have enclosed
my original diskette, a copy of the dated sales receipt, and check or money order
for $5.00 made out to Simon & Schuster, Inc. ISBN 0-13-749565-X
am ordering a 3.5-inch disk. I have enclosed my original 5.25-inch diskette along with a
check or money order for $5.00 made out to Simon & Schuster, Inc.
ISBN 0-13-747569-1
NAME__PHONE NUMBER ( ) _
ADDRESS__
CITY_STATE_ZIP
Please mail this request to: Prentice Hall Computer Publishing, 11711 N. College Avenue, Carmel, IN 46032,
ATTN: Customer Service Department. For more information call 1-800-428-5331.
PUT
FIRST
CLASS
STAMP
HERE
ISBN 0-13-747569-1
. ,TQk Please make backup copy
DS/OD utility d,*m^diate|y.
© 1992 by Peter Norton
///Brady
THE PETER NORTON PROGRAMMING LIBRARY
QuickC/IBM
Beginning/ Master QuickC for Windows
Intermediate
with Peter Norton
The essential hands-on guide for creating professional windows
applications quickly and easily. Designed for C programmers
who need to master the techniques of windows programming,
this invaluable tutorial and extended reference is filled with an
abundance of hints, tips and ideas.
Inside you'll find:
A clear and easy introduction to Windows programming
Dozens of expert Windows tips
Step-by-step instructions for creating
tiny windows programs
Valuable information on code testing, debugging
and error handling
Enclosed disk contains all the programs described in the book, plus
bonus programs including using the Windows clipboard. A
quicksort example and a color editor.
To use this disk requires the purchase and installation of
Microsoft QuickC for Windows software.
ISBN 0-13-747569-1
9780137475 698
2016-02-12 9:21
22 9780137475698
jjICKC FOR