0% found this document useful (0 votes)
273 views

QuickC For Windows

Holzner, Steven Peter Norton Computing Corporation 1992

Uploaded by

fissuras6357
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
273 views

QuickC For Windows

Holzner, Steven Peter Norton Computing Corporation 1992

Uploaded by

fissuras6357
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 470

//My THE PETER NORTON PROGRAMMING LIBRARY

Over 20 ready-to-run programs

menus, graphical output and more


X

\ > • J* \

6
QuickC for Windows
The Peter Norton Programming Series

Who This Book Is For


Introductory to intermediate QuickC for Windows programmers who want
to extend their programming expertise to QuickC for Windows and add new
performance to their programs.

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

About the Peter Norton Microcomputer Libraries


from Brady
All of the volumes in the Peter Norton Libraries, written in collaboration with
Peter Norton Computing, provide clear, in-depth discussions of the latest
developments in computer hardware, operating systems, and programming.
Fully tested and rigorously reviewed by the experts at Peter Norton Computing,
these libraries deserve a special place on your bookshelf. These libraries are
comprised of two series:

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

A Divison of Simon & Schuster, Inc.


15 Columbus Circle
New York, NY 10023

Manufactured in the United States of America


10 987654321

Library of Congress Cataloging-in-Publication Data

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

Welcome to QuickC for Windows l


About Windows 2
About Windows Programming 6
About QuickC for Windows Programming 7
Our First Window 9
Our First Windows Program 14

CHAPTER 2

Anatomy of a Windows Program 37

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

Keyboard and Mouse Input 75

Using the Keyboard in Windows 76


Reading Keystroke Messages 77
Reading Character Messages 81
Adding a Character Caret 94
The Mouse and Mouse Events 104
&

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

Graphics and a Mouse-driven Paint Program 167

Creating a Paint Template Program 167


Setting Individual Pixels 174
Freehand Drawing in Our Paint Program 175
Drawing Lines 179
Drawing Rectangles 183
Drawing Ellipses 187
Filling Figures with Color 188
“Stretching” Graphics Figures 191
Adding a Program Icon 205

CHAPTER 6

Dialog Boxes: Buttons and Textboxes 209

Message Boxes 210


Designing Dialog Boxes 213
A Calculator Example 236
A Notepad Example 252
Contents vii

CHAPTER 7

Dialog Boxes: Scroll Bars and Listboxes 267

A Control Panel Example 268


Scroll Bars 272
A Database Example 290
List Boxes 291
Combo Boxes 313

CHAPTER 8

Using Files in QuickC for Windows 315

An Open File... Dialog Box 315


A Save As... Dialog Box 335

CHAPTER 9

Fast Data Handling 367

From Arrays to Pointers 367


Fast Sorting Techniques 371
Linked Lists 388
Searching Your Data 392

CHAPTER 10

Debugging QuickC for Windows Programs 407

Using Assertions in Your Programs 407


Interactive Debugging 411
Breakpoints 415

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

Most computer and software brand names have trademarks or registered


trademarks. The individual trademarks have not been listed here.
Introduction

Why QuickC for Windows?


If you’ve done any Windows programming in C, then you know what it can be
like: difficult, time-consuming, and hard to debug. Just to get started with the
Microsoft Windows Software Development Kit (SDK) takes hours of reading
and absorbing. Writing the actual code usually takes many more hours, in¬
cluding time for experimentation and fixing problems. Graphical User Inter¬
faces — GUIs — may definitely be the wave of the future, but they can make
life very tough for the programmer.

Even the most elementary Windows application, which might do nothing


more than pop an “About” window on the screen, telling you who pro¬
grammed it, takes about five pages of code and five separate files. The enor¬
mous complexity of writing an actual, useful application slows development
time down to a snail’s pace. To make matters worse, Windows programming
tools were (until now) very far from complete — and there are times that the
Windows Software Development Kit can be as much a hindrance as a help.

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

Finally, Microsoft has introduced some Computer Aided Software Engineer¬


ing for Windows — that is, the abilities of the computer itself are finally being
put to work to help the programmer, not just the user. And this change will
help us enormously.

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.

In a nutshell, then, our approach is the programmer’s approach: task and


example oriented, without a great deal of uneccessary theory. In this book,
we’ll put QuickC for Windows to work for us, not the other way around.
Introduction xi

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!)

What’s in This Book


QuickC for Windows is a tremendous toolbox of programming resources, and
we’re going to explore them in this book. We’ll work our way up, from the
most basic examples to the most polished. In the beginning of the book, we’ll
get the essentials down, and then we’ll be able to progress beyond them. We’ll
follow the natural course of Windows programming development — starting
with an almost blank window; then we’ll embellish it a little with text. Next,
we’ll work our way up through keyboard and mouse input, and then we’ll start
working with menus. After that, we’ll spend a good deal of time learning to
use dialog boxes and the types of objects you’ll see there, such as buttons,
scroll bars, and text boxes. As we become more and more familiar with Win¬
dows programming, we’ll be able to add more and more power to our pro¬
grams; when we start working with graphics, for example, we’ll be able to
construct a mouse-driven paint program. We’ll finish up with chapters on fast
data handling and debugging.

As mentioned above, our orientation will be on seeing our programs work,


and at getting functioning results. To do that, however, we’ll have to under¬
stand what we’re doing. That means that we’ll have to take the time to under¬
stand all the concepts involved in Windows programming: concepts like
messages and message loops, resource files and special header files. In fact,
we’ll see that Windows is actually an operating system of enormous complex¬
ity.

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.

What You’ll Need


To read this book profitably, you’ll need to have some knowledge of C. This
book is primarily designed for C programmers who are starting out with
Windows; for that reason, we’ll work our way up in Windows from the begin¬
ning — but we’ll be doing it in C. You don’t need to be any kind of C expert
because we’ll work through the most difficult concepts before using them, but
if you find yourself lost in the first chapter, you should probably become more
familiar with C before continuing. The best C package to review is probably
Introduction xiii

Microsoft QuickC because, as far as straight C programming goes, it’s much


like QuickC for Windows.

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.

A Brief Windows History

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.

The MS-DOS Executive of earlier versions was replaced by a trinity of windows


that manage Windows: the Program Manager, the Task List, and the File Man¬
ager. Because we’re more interested in the programming aspects of Windows,
however, we won’t review how this user interface works; instead, let’s look
behind the scenes.

From a programming point of view, one of the most important features of


Windows 3.0 is that it can support extended memory — up to 16 megabytes of
RAM. And, in its 386- enhanced mode, Windows uses the built-in virtual mem¬
ory (that is, storing sections of memory temporarily on disk) capabilities of the
80386 to give programmers access to up to four times the amount of actual
installed memory. In a machine that has 16 megabytes, then, Windows can
actually provide 64 megabytes. The removal of memory restrictions had always
been one of the advantages of OS/2, but now it’s Windows’ domain. With
Windows 3.0, Windows had at last arrived.

The Parts of a Window


A typical Windows 3.0 window appears in Figure 1-1, and you should be famil¬
iar with the its parts before starting to program Windows applications. In fact,
as mentioned in the Introduction, if you are not a Windows user, then you
should spend time with Windows before continuing. As we’ll see below, it’s
important to know what the user expects from a Windows application before
writing one.

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

System Menu Box Title Bar Minimize Box

Maximize Box
Menu Bar

Vertical Scroll
Bar

Horizontal Scroll Bar

Client Area

Figure 1-1. A Windows 3.0 Window.

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

Figure 1-2. A Windows 3.0 Window with Menu.


Welcome to QuickC for Windows 5

(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.

Preserving the Feel of Windows


Before programming in QuickC for Windows, you should be very familiar with
the way the user expects Windows programs to work and feel. In particular,
you should be at home with the language of mouse clicks and double clicks
and be able to anticipate what the user might expect from your application.

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.

About Windows Programming


Now let’s take a look at how one programs applications for Windows, and what
makes it different from programming under DOS. To start, DOS programs
are written sequentially; that is, one event follows the other. In a DOS pro¬
gram, control goes down the list of statements, more or less in the order that
the programmer designed. For example, this is the way the standard first
program from a C book usually looks:

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

Figure 1-3. Clicker Window.

ming. That is to say, the programmer is no longer completely responsible for


the flow of the program — the user is. The user selects among all the options
presented to them, and it is up to the program to respond correctly. For
example, there may be three buttons on a window, as shown in Figure 1-3.
Clearly, we can’t just write our program assuming that the user is going to
push them in some sequence.

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.

About QuickC for Windows Programming


Under Windows, the user is king, and, until recendy, the programmer paid the
price. Programming Windows was often an excruciating task — until now. Now,
the programmer is finally benefitting from some of the same ease of application
that the user has enjoyed for so long under Windows. Now, the computer itself is
being enlisted as an aid for the programmer, not just the user.

This is a revolutionary step, and a welcome one. If you’ve programmed for


Windows before, you’ll love the way this package works. On the other hand, if
8 Peter Norton’s QuickC for Windows

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:

• Design the program’s visual interface

• Let the programming tools generate the corresponding C code

• Add the noninterface code manually

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.

Our First Window


As mentioned earlier, this is the first program you usually see in an introduc¬
tory C book:

#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

Figure 1-4. The QCWin Graphical Development Enviroment.

dows. The QuickC for Windows Graphical Development Environment win¬


dow (which we’ll call the development environment) appears, as in Figure 1-4.

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

E QuickC for Windows FF1


Fi le Edit View Project Run Debug lools Options Window Hielp
New Pts: m 1*| l&littH If lad 1-1 ft I
Open... Ctrl+FI 2
Merge... > UNTITLED 1
Close
Save Shift+Fl 2
Save As... FI 2
Save All
Print... Ctrl+Shift+Fl 2
Exit Alt+F4
1

1 C:\QCWIN\BIN\KEY.C 00006 02A


1 2 C:\QCWIN\BIN\PHONE.C >
3 C:\QCWIN\BIN\MENU1.C
4 C:\QCWIN\BIN\BUGS.C

Figure 1 -5. QCWin’s File Menu.

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

Figure 1-6. Hello, c in the Development Environment.

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.

QuickC for Windows


File Edit View Project Run Debug Tools Options Window Help
rr 1 t~i i—n
Fonts:
Project

Program Type Build Mode ~ OK


line O Windows EXE ® ^ickWin EXE (•) [)ebug
Cancel
mair O Windows DLL O DOS EXE O Release
{ Help |
Lustomize Build Uptions
}
Compiler... Linker... Resources...

i r UUUU IU 1 J

Figure 1-7. The Project Dialog Box.


Welcome to QuickC for Windows 13

QuickC for Windows


File Edit View Project Run Debug Tools Options Window Help
Fonts: Courier Restart Shift+F5
Slop 0zbtmmm AHfFB

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

Figure 1-8. The Run Menu.

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

Figure 1-9. Hello, c Window.


14 Peter Norton’s QuickC for Windows

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.

Our First Windows Program


Our goal for the rest of this chapter will be to produce a true Windows
program that prints “Hello, world.” in a window. The first step, as noted above,
is to design our program’s visual interface, and to do that, we’ll use some of
the tools that come with the QCWin package. In particular, we’ll use
QuickCase:W to design the window as we want it, and then let it produce the
C code we’ll need. The code it produces will set up and display the window on
the screen when we compile and run it in the development environment — in
reality, we’ll only have to add a few lines to put “Hello, world.” in it. However,
getting to that point will take some work.

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

Figure 1-10. QuickCase.W.


Welcome to QuickC for Windows 15

We’ll just rely on QuickCaseiW to produce the minimum C code we need to


place a simple window on the screen. We’ll add the code (i.e., customize our
program) to print “Hello, world.” later.

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

Figure 1-11. QuickCase:W’s Build menu.


16 Peter Norton’s QuickC for Windows

QuickCase:W - (HELLO.WINJ ID
Tools Options Help

p J 3j

Generate

Figure 1-12. The Generating Icon.

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.

QuickC for Windows


File Edit View Project Run Debug Tools Options Window Help
Fonts: Courier
a Pts: 10 Id MlSI
<1> C:\QCWIN\BIN\HELLO.C
* QuickCase:W KNB Version 1.00
^include "HELLO.h"

/***********************************************************
/*
Windows 3.0 Main Program Body
/*
The following routine is the Windows Main Program. The
-J. , -1——.1 i. --J i

?nr
00001 001

Figure 1-13. Editing Hello, c in QCWin.


Welcome to QuickC for Windows 17

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.

Listing 1 -1. Default Window C Code.


/* QuickCase:W KNB Version 1.00 */
#include "HELLO.h"

/' */
/* 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 */

/* 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. */
/*

/ ************************************************************************

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)

{
/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; , Show code for main window display */
/***********************************************************************/
(continued)
18 Peter Norton’s QuickC for Windows

Listing 1-1. (continued)

MSG msg; /* MSG structure to store your messages */

int nRc; /* return value from Register Classes */

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;

}
}

/* create application's Main window */

hWndMain = CreateWindow(

s zAppName, /* Window class name */


NULL, /* no title */
W S_CAPTION 1
1 /* Title and Min/Max */
WS_SYSMENU 1
1 /* Add system menu box */
WS_MINIMIZEBOX 1
1 /* Add minimize box */
WS_MAXIMIZEBOX 1
1 /* Add maximize box */
W S_THICKFRAME 1
1 /* thick sizeable frame */
WS_CLIPCHILDREN ! / * don't draw in child window */
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;

ShowWindow(hWndMain, nCmdShow); /* display main window */


Welcome to QuickC for Windows 19

Listing 1 -1. (continued)

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */

{
TranslateMessage(&msg);

DispatchMessage(&msg);

/* Do clean up before exiting from the application */

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

Listing 1-1. (continued)

/* 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 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

Listing 1-1. (continued)

/* DWORD dwExStyle; */
/* } CREATESTRUCT; */

break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


/* wParam contains a code indicating the requested sizing */
/* lParam contains the new height and width of the client area */
break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Application should draw on the client window using */


/* the GDI graphics and text functions. 'ps' the PAINTSTRUCT */
/* returned by BeginPaint contains a rectangle to the */
/* area that must be repainted. */

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

default:
/* B’or 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);

(continued)
22 Peter Norton’s QuickC for Windows

Listing 1-1. (continued)

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));

/* 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 */

/************************************************************************/

/* 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

Listing 1-1. (continued)

{
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.

This is a crucial point; every time an event occurs in Windows, a message is


sent to the currently active program, letting it know what’s happening. For
example, if a key is pressed, a special message is sent to our function
WndProc( ). That’s how our program interfaces to Windows — through mes¬
sages. Let’s take a look now at WndProc( ) — this is the function that we’ll
spend the most time modifying in this book. The prototype of WndProc( )
looks like this:

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):

WM_CREATE The window is being created


WM_KEYDOWN A key was pressed
WMSIZE Window was sized or resized
WMMOVE Window was moved
WM PAINT Window needs to be (re) drawn

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.

There are actually hundreds of predefined constants like WM CREATE


and WM KEYDOWN in the file windows.h. One way that Windows helps us
keep them straight is by giving them two- or three-letter prefixes, followed
by an underscore, as we’ll see throughout the book.

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

sage, telling us to redraw — and therefore restore — our window. This is


when we’ll print our “Hello, world.” string.

QuickCaserW has created a template version of WndProc( ) for us; without


most of the comments, it 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 */

switch (Message)
{
case WM_CREATE:
break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */

break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

SetBkMode(hDC, TRANSPARENT);

EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

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 */

HDC hDC; /* handle for the display device */


PAINTSTRUCT ps; /* holds PAINT information */
int nRc=0; /* return code */
static int my_int; <—

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

The next case is WM_PAINT, the one we want:

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_:PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

SetBkMode(hDC, TRANSPARENT);

EndPaint(hWnd, &ps) ;
break; /* End of WM_PAINT */

Professional Windows applications almost always have some code in the


WM_PAINT case. We get a WM_PAINT message whenever we have to redraw
some or all of our window; that happens if some part of our window has been
covered and is now uncovered or if the window is being drawn the first time.

If some portion of our window needs to be redrawn, that portion — which


may be the whole window — is called invalid. In Windows, these portions are
rectangles. For example, let’s say that another window was covering the lower
right corner of ours. When the user removes that window, the newly uncov¬
ered part of our window is called invalid by Windows.

invalid
28 Peter Norton’s QuickC for Windows

In this case, Windows would send us a WM_PAINT message telling us where


the invalid rectangle was in our window and ask us to redraw it. The way it tells
us what the dimensions of the invalid rectangle are is by passing that informa¬
tion to us in a specialized structure called a paint structure.

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:

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));

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:

case WM_PAINT: /* code for the window's client area */

memset(&ps, 0x00, sizeof(PAINTSTRUCT));


—» hDC = BeginPaint(hWnd, &ps) ;

This way, the BeginPaint( ) function fills the paint structure; the fields of that
structure look like this:

typedef struct tagPAINTSTRUCT


{
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL flncUpdate;
BYTE rgbReserved[16];
} PAINTSTRUCT;
Welcome to QuickC for Windows 29

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:

typedef struct tagRECT


{
int left ;
int top;
int right;
int bottom;
} RECT;

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

Note also that BeginPaint( ) has a return value, hDC:

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

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

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

—> SetBkMode(hDC, TRANSPARENT);

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.

The first time a window is displayed, a WM_PAINT message is generated and


the whole client area (the area we can draw on) is declared inactive. We can
use that WM_PAINT message to place our message at the upper left of the
window — that is, at location (0,0) as measured in pixel coordinates — and
this is how we do it with TextOut( ):

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

SetBkMode(hDC, TRANSPARENT);

—> /* USER-ADDED CODE */

—> TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */
Welcome to QuickC for Windows 31

Figure 1-14. Hello World Windows Program.

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) ;
}

In other words, while Windows programs are still difficult to write,


QuickCase:W can be a great equalizer. Let’s finish our study of WndProc( )
now. In the WM_PAINT case, we finish by releasing the handle hDC with a call
to EndPaint( ) and then we’re done:
32 Peter Norton’s QuickC for Windows

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
SetBkMode(hDC, TRANSPARENT);
/* USER-ADDED CODE */
TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));

—> EndPaint(hWnd, &ps);


break; /* End of WM_PAINT */

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM _SIZE */

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

SetBkMode(hDC, TRANSPARENT);

/* USER-ADDED CODE */
Welcome to QuickC for Windows 33

TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));

EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

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):

case WM_CLOSE: /* close the window */

—> 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 ( ):

case WM_CLOSE: /* close the window */


DestroyWindow(hWnd);
—> if (hWnd == hWndMain)
—> PostQuitMessage(0); /* Quit the application */
break;

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

termination code to PostQuitMessage( ); if the application ends normally, we


pass a value of 0.

In addition, there are many messages that we don’t handle in WndProc( ),


and we can pass them on to Windows itself to handle them for us in the default
case of WndProc( )’s switch statement:

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

SetBkMode(hDC, TRANSPARENT);

/* USER-ADDED CODE */
TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));

EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

—> 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.

That completes our window procedure WndProc( ) —and that completes


this chapter. We’ve gotten our message “Hello, world.” onto the screen in its
own window; now let’s turn to the next chapter, in which we dissect the
anatomy of the Windows program that QuickCase:W generated for us.
QuickCase:W and QCWin can handle many of the details for us, but it’s
extremely important to know what’s actually going on, because we’ll want to
modify parts of Windows programs like this later in the book.
*'
Anatomy of a Windows
Program

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)

Table 2-1. Hungarian Notation.


Anatomy of a Windows Program 39

This naming convention can help a great deal, so let’s put it to work at once
by deciphering our windows program, hello.c.

The WinMain() Program


As you may recall from last chapter, we mentioned that there are two major
parts to the C source code for our “Hello, world.” program. These two parts
were the two functions WinMain( ) and WndProc( ):

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

/* QuickCase:W KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/**************************************************** * * **************** /

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window‘display */
/********************************************************************** /

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).

The following parameter in the call to WinMain ( ), IpszCmdLine, is declared


to be a pointer of the Windows type LPSTR, which is a long pointer to a string.
In fact, if we decipher the Hungarian prefix of IpszCmdLine, we see that it is
a long pointer to a zero-terminated string. In this case, it’s a pointer to a
command line. If we were writing an editor named ed.exe to run under
Windows, and the user wanted to edit a file named, say, novel.txt, they might
enter this line to DOS: win ed novel.txt. In that case, Windows would start, the
Program Manager would stay in its iconic form, and the ed.exe window would
open. The command line passed to it, and pointed to by IpszCmdLine, would
Anatomy of a Windows Program 41

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.)

The last parameter passed to WinMain ( ) is nCmdShow, which is an integer


that indicates to our program how Windows (and therefore the user) wants us
to display our window on startup. This parameter will usually take on one of
two values: SW_SHOWNORMAL (which is defined as 1 in windows.h) or
SW_SHOWMINNOACTIVE (which is defined as 7 in windows.h). In the first
case, our window is supposed to appear fully opened; in the second case, it
should appear in its minimized or iconic state. (The SW profix stands for
Show Window.)

Now we begin the body of WinMain ( ) itself. QuickCase:W has begun by


setting aside some space for variables that we’ll need and by initializing others:

/* QuickCase:W KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
—> MSG msg; /* MSG structure to store your messages */

—> int nRc; /* return value from Register Classes */

—> strcpy(szAppName, "HELLO");


—> hlnst = hlnstance;

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):

/* QuickCase:W KNB Version 1.00 */


#include <windows.h>
#include <string.h>
42 Peter Norton’s QuickC for Windows

#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2

char szString[128]; /* variable to load resource strings */

—>char szAppName[20]; /* class name for the window */

—»HWND hlnst;
HWND hWndMain;

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);

Notice also that two constants, IDS_ERR_REGISTER_CLASS and


IDS_ERR_CREATE_WINDOW, are also defined here, as well as prototypes for
the other functions in this program: WndProc ( ), which we have seen, and two
other functions, named nCwRegisterClasses ( ) and nCwUnRegisterClasses( ),
which we will see soon.

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.

Registering a Window Class


When you register a type of window with Windows, you create a window class,
and QuickCaserW has used the name of our application (“HELLO”) as the
name of our window class. Registering our window class indicates what we
want in it: What color the background will be, what the mouse cursor will look
like in it, and so on. After registering a class in a Windows session (i.e., the
time after typing Win<cr> to DOS and before you exit Windows back to DOS),
we don’t have to register it again. Instead, we can just indicate that we want a
window of that class to be created because Windows will know about that class
already. For that reason, we’ll only register our class of window, HELLO, if
there are no previous instances of our program running — that is, if
hPrevInstance = NULL — because previous instances would have already reg¬
istered our class. In WinMain( ), that looks like this, where QuickCase:W has
used the function nCwRegisterClasses ( ) to register our window:
Anatomy of a Windows Program 43

/* QuickCase:W KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
MSG msg; /* MSG structure to store your messages */
int nRc; /* return value from Register Classes */

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 */

}
}

The function nCwRegisterClasses( ) appears at the end of hello.c like this:

hello.c

WinMain ( )

WinProc( )

CwRegisterClasses( )

This function registers our window by filling a structure of type WNDCLASS


and passing it to the Windows function RegisterClass( ). The WNDCLASS
structure is defined this way in windows.h (and it’s our job to fill all these fields
before calling RegisterClass( ) and registering the class):
44 Peter Norton’s QuickC for Windows

typedef struct tagWNDCLASS


{
WORD style;
LONG (FAR PASCAL *lpfnWndProc)();
int cbClsExtra;
int cbWndExtra;
HANDLE hlnstance;
HICON hlcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPSTR IpszMenuName;
LPSTR IpszClassName;
} WNDCLASS;

We begin in nCwRegisterClasses( ) by setting up and clearing such a structure,


which we name wndclass:

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));

/* load WNDCLASS with window's Characteristics */


wndclass.style = CS_HREDRAW ! CS_VREDRAW ! C S_BYTEALIGNWINDOW; <—
wndclass.lpfnWndProc = WndProc; <—

The window class style we choose is made up from a combination of constants,


all of which are defined in Windows.h: CS_HREDRAW ! CS_VREDRAW !
CS_BYTEALIGNWINDOW. The prefix here stands for Class Style, and the
first two constants indicate that we want the window’s client area redrawn (i.e.,
a WM PAINT message will be sent) when either the vertical or horizontal size
Anatomy of a Windows Program 45

of the window is changed. CS BYTEALIGNWINDOW is a performance flag


that indicates Windows should align the window on a byte boundary; its use is
optional. Here are the available CS constants:

CS_VREDRAW Redraw if client area height changes


CSJHREDRAW Redraw if client area width changes
CS_DBLCLKS Class can receive and process double clicks
CS_OWNDC Unique display context for class’s windows
CS_CLASSDC Allocate one DC for all windows of this class
CS_PARENTDC Give parent’s display contex to this class
CS_NOCLOSE Inhibit close option in sys menu
CSSAVEBITS Save windows under ours bit by bit
CS_BYTEALIGNCLIENT Align client area on byte boundary
CS_BYTEALIGNWINDOW Align window on byte boundary
CS GLOBALCLASS Declare an application global class

The CS_SAVEBITS constant is an interesting performance flag; it is used


for windows that are only supposed to appear briefly on the screen (much
like a dialog box). This style of window saves the screen pixels of other
windows that it covers up when it appears, and restores them after it closes.
The reason that this helps performance is that no WM_PAINT message has
to be sent to the underlying windows so that they can restore their displays.

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));

/* load WNDCLASS with window's characteristics */


wndclass.Style = CS_HREDRAW ! CS_VREDRAW ! CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc; <—
46 Peter Norton ’s QuickC for Windows

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));

/* load WNDCLASS with window's characteristics */


wndclass.style = CS_HREDRAW ! CS_VREDRAW I CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra =0; <—
wndclass.cbWndExtra =0; <—
wndclass.hlnstance = hlnst; <—
wndclass.hlcon = Loadlcon(NULL, IDI_APPLICATION); <—
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); <—

IDI APPLICATION stands for the default Windows application icon, as


shown in Figure 2-1 (IDI stands for ID of an icon). QuickCase:W has used the
Windows function Loadlcon ( ) to load this icon into our application. In addi¬
tion, it uses the Windows function LoadCursor( ) to load our application’s
mouse cursor, which is simply the default arrow that Windows users are accus¬
tomed to and is indicated by the constant IDC_ARROW (where IDC stands for
Anatomy of a Windows Program 47

□Hello

Figure 2-1. Default Windows Application Icon.

ID of a cursor). Other predefined possibilities for the mouse cursor include


these:

IDC_ARROW Standard arrow cursor


IDC_IBEAM I beam cursor
IDC_WAIT Hourglass
IDC_CROSS Cross
IDC_UPARROW Up arrow
IDC_SIZE Sizing arrow
IDCJCON Icon
IDC_SIZENWSE Northwest-southeast sizing arrow
IDC_SIZENESW Northeast-southwest sizing arrow
IDC.SIZEWE East-west sizing arrow
IDC SIZENS North-south sizing arrow

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));

/* load WNDCLASS with window's characteristics */


wndclass.Style = CS_HREDRAW ! CS_VREDRAW ! CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra = 0;
48 Peter Norton’s QuickC for Windows

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); <—

The constant COLOR_WINDOW+l stands for the current color of windows


being used in this session of Windows, and we cast that into a brush
handle with the type override HBRUSH, and then place it in the field
wndclass.hbrBackground.

One unusual aspect here is that we had to pass COLOR_WINDOW+l here,


not simply COLOR_WINDOW. Adding 1 to COLOR_WINDOW to get the
actual color used for windows is a Windows convention, perhaps to make
sure that we don’t pass a zero value.

As we’ll see in later chapters, there are a number of predefined graphics


objects in Windows, referred to as stock objects, and if we had wanted to make
sure that the background of our window was, say, white (instead of the current
background window color that the user may have set using the Windows
control panel), we could have used the WHITE BRUSH stock object like this:
wndclass. hbrBackground = GetStockObject(WHITEBRUSH);.

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.

In addition, QuickCase:W uses the same string, szAppName, as the name of


the window class we are generating (so the name of our window class will be
HELLO), and we pass that in the variable wndclass.IpszClassName. Finally, we
register the window class with RegisterClass( ) and, to finish
nCwRegisterClasses( ), we return a code — 0 if everything worked, -1 other¬
wise:
Anatomy of a Windows Program 49

Returning a 0 is Windows’ standard method of indicating that no error


occurred.

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 I CS_VREDRAW ! CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra = 0;
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);
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 */

At this point, we’re done with nCwRegisterClasses( ), and we return to


WinMain( ). There, the first thing we must do is to check if the registration
was a success. If not, we want to display an error message and quit, since we
cannot continue. That works like this in WinMain( ):

/* QuickCaserW KNB Version 1.00 */


ttinclude "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
MSG msg; /* MSG structure to store your messages */
int nRc; /* return value from Register Classes */

strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance)
{
50 Peter Norton’s QuickC for Windows

/* 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;


}
} ft

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:

/* QuickCase:W KNB Version 1.00 */


#include <windows.h>
#include <string.h>
—> #define IDS_ERR_REGISTER_CLASS 1
—> #define IDS_ERR_CREATE_WINDOW 2
Anatomy of a Windows Program 51

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */


HWND hlnst;
HWND hWndMain;

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


int nCwRegisterClasses(void) ;
void CwUnRegisterClasses(void);

The file hello.rc also includes hello.h, so it knows that IDS_ERR_REGIS-


TER_CLASS equals 2:

#include "HELLO.h" <—

STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class"
END

Resource strings are stored in a string table, as indicated by the entry7


STRINGTABLE above, and enclosed by BEGIN and END keywords. This is
typical of resources; we’ll see that menus are indicated by the keyword MENU
(e.g., the name of the menu will come first, followed by all the items in that
menu; the complete menu definition must appear in the resource file so it can
be loaded with LoadMenu( )). In WinMain( ), we can use the string
IDS_ERR_REGISTER_CLASS by loading it in with LoadString( ) and by plac¬
ing it into a string in memory named szString:

/* QuickCase:W KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
MSG msg; /* MSG structure to store your messages
int nRc; /* return value from Register Classes */

strcpy(szAppName, "HELLO");
hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
52 Peter Norton’s QuickC for Windows

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;
}
}

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:

/* QuickCase:W KNB Version 1.00 */


tinclude "HELLO.h"
Anatomy of a Windows Program 53

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
MSG msg; /* MSG structure to store your messages */
int nRc; /* return value from Register Classes */

s t rcpy(s zAppName, "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;
}
}

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */

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

indicate what we want to CreateWindow( ), we just OR our flags together like


this:

Window Style Means

WS_OVERLAPPED Overlapped window with caption and border


WS_POPUP Pop-up window (do not use with WS_CHILD)
WS_CHILD A child window
WS_MINIMIZE Window of minimum size
WS_VISIBLE Makes window initially visible
WS_DISABLED Makes window initially disabled
WS_CLIPSIBLINGS Clip children relative to each other
WSCLIPCHILDREN Do not redraw children
WSMAXIMIZE Window of maximum size
WSCAPTION WS_BORDER ! WSJDLGFRAME
WS_BORDER Add a border
WS_DLGFRAME Use dialog box border
WSVSCROLL Add vertical scroll bar
WS_HSCROLL Add horizontal scroll bar
WS_SYSMENU Add a system menu
WS_THICKFRAME Use thick border
WS_GROUP Member of a group of controls
WSTABSTOP Can receive the input focus
WS_MINIMIZEBOX Add a minimize box
WSMAXIMIZEBOX Add a maximize box
WSTILED WS_OVERLAPPED
WSICONIC WS_MINIMIZE
WSSIZEBOX WSJTHICKFRAME
WS_OVERLAPPEDWINDOW WS_OVERLAPPED I WS_CAPTION ! \
WS_SYSMENU 1 WSJTHICKFRAME ! \
WS_MINIMIZEBOX ! WS_MAXIMIZEBOX
WSPOPUPWINDOW WS_POPUP ! WS_BORDER ! WS_SY5MENU
WS_CHILDWINDOW WSCHILD
WSTILED WINDOW WS_OVERLAPPEDWINDOW
WS_EX_DLGMODALFRAME Dialog box can have title and system menu

1 able 2-2. Window Styles for Use with CreateWindow( ).


Anatomy of a Windows Program 55

/* QuickCase:W KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
MSG msg; /* MSG structure to store your messages */
int nRc; /* return value from Register Classes */

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;
}
}

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */
NULL, /* no title */

WS_CAPTION ! /* Title and Min/Max */


WS_SYSMENU ! /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
WS_THICKFRAME ! /* thick sizeable frame */
WS_CLIPCHILDREN ! /* don't draw in child windows */
WS_OVERLAPPED,

Because using many of these flags is so common, Windows provides the


WS_OVERLAPPEDWINDOW style, which is equal to: WS_CAPTION !
WS_SYSMENU ! WS_MINIMIZEBOX ! WS_MAXIMIZEBOX !
WS THICKFRAME ! WS OVERLAPPED.

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( )):

/* QuickCase:W KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
MSG msg; /* MSG structure to store your messages */
int nRc; /* return value from Register Classes */

s t rcpy(s zAppName, "HELLO");


hlnst = hlnstance;
if(!hPrevInstance)
{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) = = -1)
{
Anatomy of a Windows Program 57

/* 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(
szAppName, /* Window class name */
NULL, /* no title */
WS_CAPTION 1
1 /* Title and Min/Max */
WS_SYSMENU 1
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 */

That’s it; now our window is created — if everything worked as expected.


However, there may have been some error in creating the window (we might
have run out of memory, for example), so we check to see if the returned
window handle is NULL (which is Windows’ method of indicating that a
handle is invalid). If so, we display an error message, much as we did with the
“Error registering window class” message — except this time, we use the other
error message (as defined in our resource file, hello.rc): IDS_ERR_CRE-
ATE WINDOW, which stands for the string: “Window creation failed!” As
before, we place that message in a message box and then quit if there was an
error:

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */
NULL, /* no title */
WS_CAPTION /* Title and Min/Max */
WS_SYSMENU /* Add system menu box */
WS_MINIMIZEBOX /* Add minimize box */
WS MAXIMIZEBOX /* Add maximize box */
58 Peter Norton ’s QuickC for Windows

WS_THICKFRAME ! /* 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;
}

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.

Showing the Window on the Screen

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

fully opened or in its iconic state, and it was already passed to us in


WinMain( ), as you may recall:

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow) <—

And that means that the call to ShowWindow( ) looks like just this:

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */
NULL, /* no title */
WS_CAPTION ! /* Title and Min/Max */
WS_SYSMENU ! /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
WS_THICKFRAME ! /* 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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

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

The Message Loop


The way messages are handled in Windows is by filling the fields of a MSG
structure. That structure is defined this way (in windows.h):

/* 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:

typedef struct tagPOINT


{
int x;
int y;
} POINT;

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

ShowWindow(hWndMain, nCmdShow); /* display main window */

// Begin message loop!


while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */
{

The first parameter we pass to GetMessage ( ) is the address of our MSG


structure, which QuickCaseiW called msg at the top of WinMain( ):

/* QuickCasetW KNB Version 1.00 */


#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
—» MSG msg; /* MSG structure to store your messages */
int nRc; /* return value from Register Classes */

The next parameters to GetMessage ( ) are all set to NULL or 0, indicating


that we want all messages directed towards our window (i.e., it is possible to
receive only a subset of all messages if we want).

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:

ShowWindow(hWndMain, nCmdShow); /* display main window */


while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */
{
—> TranslateMessage(&msg);

}
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:

ShowWindow(hWndMain, nCmdShow); /* display main window */


while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */

{
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.)

Exiting the Application


You might recall that when our main window is closed — that is, we get a
WM CLOSE message — QuickCaserW has designed our window procedure,
WndProc( ), to execute this code (see Listing 1-1 in the last chapter, or Listing
2-2 later in this one):

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
DestroyWindow(hWnd)f
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;
64 Peter Norton’s QuickC for Windows

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:

• »

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */

{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */

CwUnRegisterClasses() ; <—

return msg.wParam; <—


} /* End of WinMain */

The CwUnRegisterClasses( ) function simply unregisters our window class in


order to free memory, and it appears at the end of hello.c:

hello.c

WinMain ( )

Message Loop:
Windows messages Exit

WndProc( )
switch

DefWindowProc( )

CwRegisterClasses( )

CwUnRegisterClasses( )
Anatomy of a Windows Program 65

This function, cwUnRegisterClasses( ), was written by QuickCase:W to unregis¬


ter our class (and therefore free the associated memory), which it does with
the Windows function UnregisterClass( ), like this:

void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));

UnregisterClass(szAppName, hlnst); <—


} /* End of CwUnRegisterClasses */

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:

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam; <—
} /* End of WinMain */

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).

Listing 2-1. WinMain() for Hello.c.


/* QuickCaserW KNB Version 1.00 */
#include "HELLO.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)

MSG msg; /* MSG structure to store your messages */

int nRc ; /* return value from Register Classes */

st ropy(s zAppName, "HELLO");


hlnst = hlnstance;
(continued)
66 Peter Norton’s QuickC for Windows

Listing 2-1. (continued)

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(
szAppName, /* Window class name */
NULL, /* no title */
WS_CAPTION I /* Title and Min/Max */
WS_SYSMENU I /* Add system menu box */
WS_MINIMIZEBOX i /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
WS_THICKFRAME ! /* thick sizeable frame */
WS_CLIPCHILDREN I /* 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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Anatomy of a Windows Program 67

Listing 2-1. (continued)

/* Do clean up before exiting from the application


CwUnRegisterClasses();
return msg.wParam;
} /* End of WinMain

************************************************************************ /
/* */
/* 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 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

Listing 2-1. (continued)

/* 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 */

The Window Procedure


The other main part of the C code for a Windows program is, as we already
know, the window procedure, WndProc( ). As we’ve seen, for most messages,
control goes something like this (note that, actually, most messages are passed
back to Windows when we pass them to the default window procedure,
De 1 WindowProc( )):

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


break;

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

Listing 2-2. WndProc() Function for Hello.c.


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)
{
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; */

break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


/* wParam contains a code indicating the requested sizing */
/* lParam contains the new height and width of the client area */
break; /* End of WM SIZE */
Anatomy of a Windows Program 71

Listing 2-2. (continued)

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps) ;

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Application should draw on the client window using */


/* the GDI graphics and text functions. 'ps' the PAINTSTRUCT */
/* returned by BeginPaint contains a rectangle to the */
/* area that must be repainted. */

/* USER-ADDED MESSAGE */
TextOut (hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, \
world."));

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */

However, there are a number of other files involved in the creation of


hello.exe, and all of them are generated by QuickCase:W for us automatically
when we design our window and generate our source code:
72 Peter Norton’s QuickC for Windows

QuickCase.W

hello.c hello.rc hello.h hello.def hello.mak

C Code Resource Include Linker Make File


file file file file (Used by
QCWin)

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

CODE PRELOAD MOVEABLE <r~

DATA PRELOAD MOVEABLE MULTIPLE <r~

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~

STACKSIZE 5110 <r~

EXPORTS WndProc @1 <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.

QuickC for Windows Projects


As you may have noticed, there are quite a number of files to juggle here.
However, one of the ideas behind the integrated development environment
in QuickC for Windows is to make this process as easy as possible (in fact, as
much like working with DOS QuickC as possible) — so it keeps track of all
these files for us. As you recall, all we had to do to run our program was to
select the Go item in the Run menu. The way it keeps track of our programs is
by dividing them up into projects. In our case, our project is called HELLO, and
all the information for this project (i.e., what files are involved) are stored in
the file hello.mak, which QuickCase:W generates and QuickC for Windows
uses as the basis for the HELLO project:

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).

That completes our coverage of the general anatomy of a Windows program


under QuickC for Windows. Now let’s start seeing more visible results im¬
mediately when we write programs which deal with keyboard and mouse input
in the next chapter.

QuickC for Windows


Project Run Debug Tools Options Window Help
Open...
Edit- HELL02.MAK
■J' 1^1 1 r J\ I
Close
Compile File
Build HELL02.EXE
Rebuild All HELL02.EXE
Load Workspace HELL02.WSP ►
Save Workspace HELL02.WSP ►
1 C:\QCWIN\BIN\HELL02.MAK
2 C:\QCWIN\BIN\HELLO.MAK
3 C:\QCWIN\BIN\KEY.MAK
A C . .\B 1 N\TE STIN G 4. MAK

Figure 2-2. QuickC for Windozus Project Menu.


Keyboard and Mouse Input

In the previous chapter, we learned about the anatomy of a Windows pro¬


gram, a necessary but not exactly exciting step towards becoming Windows
programmers. In this chapter, we’ll see a little more action. Here, we’re going
to start accepting user input from both the keyboard and the mouse.

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.

Using the Keyboard in Windows


Even in a multitasking windowing environment, we still only have one key¬
board. That means that even though we might have a dozen windows dis¬
played on the screen at once, only one can actively be accepting keyboard
input. In Windows, we say that that window has the current focus. And the
window that has the focus is either the currently active window or a child
window of the active window. It’s easy to tell which window is currently active
in a Windows session; if the window has a caption bar at the top, Windows
highlights it. If the window does not have a caption bar, Windows highlights
the window’s frame. In fact, even an icon can be the active window, and if it is,
Windows highlights the caption text underneath it. When you type using the
keyboard, a stream of keyboard messages is sent to the window with the focus.

Windows Keyboard Input Conventions


In Windows, there is usually a flashing cursor to indicate where text is going to
go when you type it (often either a vertical bar or an I beam shape). We should
note that, in Windows, the mouse cursor is usually referred to as the cursor,
while what we call the cursor in DOS is called the insertion point or caret in
Windows. That is, text appears as the insertion point or caret after you type it,
and when you move the mouse, you’re moving the cursor (not the insertion
point or caret).

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

general, the user is supposed to be able to circulate around a window’s con¬


trols simply by pressing the tab button. You might also note that when a dialog
box first appears, one of the buttons is often highlighted already (often the
button marked OK); in that case, we say that that button has the default focus.

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.

Reading Keystroke Messages


There are only four keystroke messages, and they are sent to the window with
the current focus:

WM_KE YD OWN Key was pressed

WM_SYSKEYDOWN System key was pressed

WM_KEYUP Key was released

WM SYSKEYUP System key was released

Ax you might expect, WM KEYDOWN is generated when a key is struck,


WM_KEYUP when the key is released. In addition, Windows makes a distinc¬
tion for system keystrokes (the keystrokes that are commands to Windows,
usually in combination with the Alt key, including Alt-Esc, which switches the
active window). Those messages are: WM_SYSKEYDOWN and WM SYS-
78 Peter Norton’s QuickC for Windows

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

previous key state OEM scan code repeat count


context code extended

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

VK_LBUTTON VK_NEXT VKNUMPAD2 VKF3


VK_RBUTT ON VK_END VKNUMPAD3 VKF4
VK_CANCEL VK_HOME VKNUMPAD4 VK_F5
VK_MBUTTON VK_LEFT VK_NUMPAD5 VKF6
VK_BACK VKUP VKNUMPAD6 VK_F7
VK_TAB VK_RIGHT VKNUMPAD 7 VKF8
VK_CLEAR VKDOWN VKNUMPAD8 VKF9
VK_RETURN VK_SELECT VK_NUMPAD9 VKF10
VKSHIFT VKPRINT VK_MULTIPLY VKFll
VKCONTROL VKEXECUTE VK_ADD VK_F12
VK_MENU VKSNAPSHOT VK_SEPARAT OR VKF13
VKPAUSE VKJNSERT VK_SUBTRACT VKF14
VK CAPITAL VK_DELETE VK_DECIMAL VKF15
VKESCAPE VK_HELP VKDIVIDE VK_F16
VKSPACE VKNUMPADO VKFl VK_NUMLOCK
VKJPRIOR VK_NUMPAD 1 VK_F2 VKA-VKZ, VKO-VK9

Table 3-1. Virtual Key Codes.

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:

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{

—> TranslateMessage(&msg);
DispatchMessage(&msg);
}

The purpose of this function is to turn WMKEYDOWN messages into some¬


thing we can use by generating WM_CHAR messages. In other words, the
normal stream of messages for every keystroke is WM KEYDOWN,
Keyboard and Mouse Input 81

WM_CHAR, and WMKEYUP, because TranslateMessage( ) translates the


WM_KEYDOWN message and produces a WM CHAR message. And the
WM_CHAR message is the one we’ll usually use to read from the keyboard.

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.

Reading Character Messages


When we receive a WM_CHAR message, the typed character’s ASCII code is
stored in the wParam parameter, which means that we can use it much as we
would individual keystroke information in C programs under DOS. For exam¬
ple, we can compare wParam to ASCII characters like ‘a’ or ‘z,’ or even against
codes like ‘\t’ (a tab) or V’ (a carriage return).

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

Figure 3-1. Key.win’s Template.


82 Peter Norton’s QuickC for Windows

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]

|_OK__| | Cancel \ | Help

Figure 3-2. QuickCase:W’s Title Dialog Box.

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


break;

default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}

And adding a WM_CHAR case will not be very difficult:

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

QuickC for Windows


\ File Edit View Project Run Debug Tools Options Window Help
I Fonts: Courier Pts: 10
a 1]

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

Figure 3-3. Editing Key. c.


84 Peter Norton’s QuickC for Windows

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_PAINT: /* code for the window's client area */


—> hDC = BeginPaint(hWnd, &ps) ;

—> EndPaint(hWnd, &ps);


break; /* End of WM_PAINT */
»

However, BeginPaint( ) only returns a device context handle if a part of our


window has been declared invalid, and even then it only returns a handle to
the invalid rectangle. There is another way of getting a device context handle,
one that we can be sure includes our window’s entire client area, and that is by
using GetDC( ). (Together, BeginPaint( ) and GetDC( ) are the two most
common ways of getting device context handles in Windows.) Using
GetDC( ), our handle, hDC, will refer to the entire client area of our window.
At the end of our WM_CHAR case, we can release it with ReleaseDC( ):

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

Note that since nCharacters is static, it is guaranteed to start at 0 when the


program begins, even if we hadn’t initialized it to 0. Next, we fill cBuffer[ ]
with the incoming character from wParam, and increment nCharacters:

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_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */
86 Peter Norton’s QuickC for Windows

—» 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;

case WM_.PAINT: /* code for the window's plient area */


break; /* End of WM_PAINT */

case WM_.CLOSE: /* close the window */


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:

typedef struct tagRECT


{
int left ;
int top ;
int right;
int bottom;
} RECT;

Another popular way of determining graphic dimensions is with


GetSystemMetrics(nlndex), where nlndex corresponds to one of the Win¬
dows.h constants as shown in Table 3-2. Simply pass the constant you’re inter¬
ested in learning about — such as SM CXICON (SM stands for System
Metrics) to learn the horizontal width of an icon — and GetSystemMetrics( )
will return that value in its integer return value.
Keyboard and Mouse Input 87

Figure 3-4. Key. c at Work.

SM Constant Means

SM_CXSCREEN Width of screen


SM_CY5CREEN Height of screen
SMCXVSCROLL Width of scroll bar
SMCYHSCROLL Height of scroll bar
SMCYCAPTION Height of caption
SM_CXBORDER Width of borders
SM_CYBORDER Height of borders
SM_CXDLGFRAME Width of dialog box frames
SM_CYDLGFRAME Height of dialog box frames
SM_CXHTHUMB Width of scroll bar thumbs
SMCWTHUMB Height of scroll bar thumbs
SMCXICON Width of icon
SMCYICON Height of icon
SMCXCURSOR Width of cursor
SMCYCURSOR Height of cursor
SM_CYMENU Height of menu bar
SM_CXFULLSCREEN Width of full screen
SM_CYFULLSCREEN Height of full screen
SM_CYKANJIWINDOW Width of Kanji window
SM_MOUSEPRESENT Nonzero if there is a mouse
SM CYVSCROLL Height of scroll bar

Table 3-2. System Metrics Constants.


88 Peter Norton’s QuickC for Windows

SM_CXHSCROLL Width of scroll bar


SMJDEBUG Nonzero if in Windows debugging version
SM_SWAPBUTTON Nonzero if left and right buttons swapped
SM_RESERVED1 Reserved
SM_RESERVED2 Reserved
SM_RESERVED3 Reserved
SM_RESERVED4 Reserved
SM_CXMIN Minimum width of window
SM_CYMIN Minimum height of window
SM_CXSIZE Width of bitmaps in title bar
SM_CYSIZE Height of bitmaps in title bar
SM_CXMINTRACK Minimum tracking width of window
SM_CYMINTRACK Minimum tracking height of window

Table 3-2. (continued)

Note that a quick way to determine if there is a mouse present in the


computer you’re working with is by using the return value of
GetSystemMetrics(SM_MOUSEPRESENT). If nonzero, a mouse is present.

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.

Listing 3-1. Key.c Reads and Displays Keys.


/* QuickCase:W KNB Version 1.00 */
#include "KEY.h"

/ **************************** ******************************************** /
/★ */
/ * Windows 3.0 Main Program Body */

/ */
Keyboard and Mouse Input 89

Listing 3-1. (continued)

/* 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. */
/* */
/************************************************************************/

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/
★/
/* HANDLE hlnstance; handle for this instance
handle for possible previous instances ★/
/* HANDLE hPrevInstance;
/* LPSTR IpszCmdLine; long pointer to exec command line */

/* int nCmdShow; Show code for main window display ★/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

st rcpy(s zAppName, "KEY") ;


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 */

(continued)
90 Peter Norton’s QuickC for Windows

Listing 3-1. (continued)

"Key Input", /* Window's title */


WS_CAPTION 11
/* Title and Min/Max */
WS_SYSMENU 1
1 /* Add system menu box */
WS_MINIMIZEBOX 11
/* Add minimize box */
WS_MAXIMIZEBOX 1
1 /* Add maximize box */
W S_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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0) ) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* End of WinMain */
Z************************************************************************/
/* */
/* 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 */


Keyboard and Mouse Input 91

Listing 3-1. (continued)

/* 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

Listing 3-1. (continued)

{
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; */

break; /* End of WM_CREATE */

case! WM_MOVE: /* code for moving the window */


break;

case: WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

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

Listing 3-1. (continued)

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will send WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Application should draw on the client window using */


/* the GDI graphics and text functions. 'ps' the PAINTSTRUCT*/
/* returned by BeginPaint contains a rectangle to the */
/* area that must be repainted. */

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window*/
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

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

Listing 3-1. (continued)

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 ! CS_BYTEALIGNWINDOW;
wndclass.IpfnWndProc = 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 */

Adding a Character Caret


For all of key.c s deficiencies, one stands out: There is no blinking insertion
point or caret that indicates where the text will go. Usually in Windows, we
have some indication of where the characters we type are going to be put. As
Keyboard and Mouse Input 95

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:

break; /* End of 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

typedef struct tagTEXTMETRIC


{
int tmHeight;
int tmAscent;
int tmDescent;
int tmlnternalLeading;
int tmExternalLeading;
int tmAveCharWidth;
int tmMaxCharWidth;
int tmWeight;
BYTE tmltalic;
BYTE tmUnderlined;
BYTE tmStruckOut;
BYTE tmFirstChar;
BYTE tmLastChar;
BYTE tmDefaultChar ;
BYTE tmBreakChar;
BYTE tmPitchAndFamily;
BYTE tmCharSet;
int tmOverhang;
int tmDigitizedAspectX;
int tmDigitizedAspectY;
} TEXTMETRIC;

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;

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */

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.

Figure 3-5. Key2. c with Caret.


100 Peter Norton’s QuickC for Windows

Listing 3-2. Key2.c — Displays Keystrokes and Caret.


/* QuickCase:W KNB Version 1.00 */
#include "KEY2.h"

/************************************************************************/
%

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)

/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

st rcpy(s zAppName, "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);
return nRc;
}
}

/* create application's Main window */


hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Key Input", /* Window's title */
WS_CAPTION /* Title and Min/Max */
WS_SYSMENU /* Add system menu box */
WS_MINIMIZEBOX /* Add minimize box */
WS_MAXIMIZEBOX /* Add maximize box */
W S_THICKF RAME /* thick sizeable frame */
WS_CLIPCHILDREN /* don't draw in child windows */
WS_OVERLAPPED,
CW_US EDEFAULT, 0 /* Use default X, Y */
CW_USEDEFAULT, 0, /* Use default X, Y */
Keyboard and Mouse Input 101

Listing 3-2. (continued)

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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* End of WinMain */

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

Listing 3-2. (continued)

CreateCaret(hWnd, NULL, cxChar/4, cyChar);


SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


/* wParam contains a code indicating the requested sizing */
/* lParam contains the new height and width of the client area */
break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Application should draw on the client window using */


/* the GDI graphics and text functions. 'ps' the PAINTSTRUCT */
/* returned by BeginPaint contains a rectangle to the */
/* area that must be repainted. */

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

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;

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
Keyboard and Mouse Input 103

Listing 3-2. (continued)

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));

/* 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 = Loadlcon(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;

(continued)
104 Peter Nortons QuickC for Windows

Listing 3-2. (continued)

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 Mouse and Mouse Events


As you might expect, mouse events such as clicking, double clicking, or mov¬
ing are all communicated to us through Windows messages like these:

WMMOU SEMOVE Mouse was moved


WMLBUTT ONUP Left button up
WMLBUTTONDBLCLK Left button double click
WMLBUTTONDOWN Left button down
WMRBUTTONUP Right button up
WMRJBUTTONDBLCLK Right button double click
WM_RBUTTONDOWN Right button down
Keyboard and Mouse Input 105

WM MBUTTONUP Middle button up


WM MBUTTONDBLCLK Middle button double click
WM_MBUTTONDOWN Middle button down

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).

If you want your program to receive double click messages (e.g.,


WM_LBUTTONDBLCLK), which are normally reserved for controls like
buttons and listboxes, you must include the window class style constant
CS_DBCLKS in the definition of your window’s class style something like
this: wndclass.style = CS_HREDRAW ! CS_VREDRAW ! CS_BY-
TEALIGNWINDOW ! CS_DBCLKS;.

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);

A POINT structure is used by some functions and is defined, as we’ve seen,


like this in Windows:

typedef struct tagPOINT


{
int x;
int y;
} POINT;

We should also note that Windows does not generate a WM MOUSEMOVE


message for every pixel location over which the mouse cursor travels. Instead,
106 Peter Norton’s QuickC for Windows

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):

WMNCMOUSEMOVE Nonclient mouse move


WM_NCLBUTTONDOWN Nonclient left button down
WMNCLBUTTONUP Nonclient left button up
WM_NCLBUTTONDBLCLK Nonclient left button double click
WM_NCRBUTTONDOWN Nonclient right button down
WM_NCRBUTTONUP Nonclient right button up
WM_NCRBUTTONDBLCLK Nonclient right button double click
WM_NCMBUTTONDOWN Nonclient middle button down
WM_NCMBUTTONUP Nonclient middle button up
WM NCMBUTTONDBLCLK Nonclient middle button double click

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

I client area coordinates

screen area coordinates


Keyboard and Mouse Input 107

Using the Mouse in Code


Using direct mouse information is made very easy through the use of mouse
messages. In our case, we just want to move the text insertion point to a new
location on the screen. We start at (0,0):

When the caret is there, we can type our messages:

Next, we might move the mouse cursor somewhere else and click it. The caret
should then move to that location:

And we should be able to type again:

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

Figure 3-6. Key2. c with Key Input Title.

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.

Figure 3-7. Key2.c with Mouse Input Title.


Keyboard and Mouse Input 109

QuickCase:V■/ - (KEY2.WIN)* E
UI le Design Build Tools Options Help
Generate jt
F . 33
..«

<<>> YourWindo\J Update s Here

Figure 3-8. QuickCase:W’s Update Menu Item.

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)

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 */


110 Peter Norton’s QuickC for Windows

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

Listing 3-3. Mouse Input Application.

/* QuickCaserW KNB Version 1.00 */


#include "KEY2.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/I**********************-*************************************************
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display * //
/*********************************************************************** j

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

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

Listing 3-3. (continued)

return nRc;
}

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */
"Mouse Input", /* Window's title */
WS_CAPTION 1
1 /* Title and Min/Max */
WS_SYSMENU 1
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_U S E DE FAUL T, 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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* End of WinMain */

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam)

(continued)
114 Peter Norton’s QuickC for Windows

Listing 3-3. (continued)

{
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, 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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


/* wParam contains a code indicating the requested sizing */
/* IParam contains the new height and width */
break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Application should draw on the client window using */


/* the GDI graphics and text functions. 'ps' the PAINTSTRUCT*/
/* returned by BeginPaint contains a rectangle to the */
/* area that must be repainted. */

/* Inform Windows painting is complete */


Keyboard and Mouse Input 115

Listing 3-3. (continued)

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;

case WM_CLOSE: /* close the window */


DestroyCaret();
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0);
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

Listing 3-3. (continued)

/* 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 */

/************************************************************************/

/* 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)

File Edit (Menu Bar) Help

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.

Adding Menus to Our Programs


Let’s begin by starting QuickCase:W, as shown in Figure 4-1. Give the new
window the caption Menu Input by clicking the caption bar and filling in the
text box in the Title dialog box. In addition, let’s give this program the name
Menus 119

QuickCase:W - (Untitled]
File Design Build loots Options Help

«>>
Your Window's Title Goes Here
Your Window's Menu Goes Here
33

Figure 4-1. QuickCase:W’s Window.

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

Insert Menu Item



Style
itled)*
Options Help
E
Fi ®! String]
O Separator
O Bitmap lere
33.
Name:

Aecei editor:

"Initial State'
’T*) Active

O Grayed

Link to
Dropdown Menu 21
.link... |

OK | Cancel ] Help ]

Figure 4-2. QuickCase:W’s Insert Menu Item for Menus.

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 «»

«»

Figure 4-3. Our New File Menu.

Insert Menu Item

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 |

Figure 4-4. QjuickCaseiW’s Insert Menu Item for Menu Items.


122 Peter Norton’s QuickC for Windows

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
SetBkMode(hDC, TRANSPARENT);
EndPaint(hWnd, &ps);
break;

case WM_CLOSE: /* close the window */


DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0);
break;
Menus 123

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 */

QuickCase:W has added a case in this inner switch statement named


IDM_F_HELLO. IDM stands for ID of a menu; the F stands for File menu; and
HELLO is the name we gave to our menu item. In other words, when the user
selects the Hello item in our File menu, the program executes the code in the
IDM_F_HELLO case. Which means that this is where we should put our code
to place the message “Hello, world.” in the window.

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:

—» return DefWindowProc(hWnd, Message, wParam, lParam);

break; /* End of WM_COMMAND */


124 Peter Norton's QuickC for Windows

QuickC for Windows


Options Window Help
1
Menu Input
File
^|jg| 1 IH Mill
Hello, world.

Figure 4-5. Our First Working Menu Program.

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

Listing 4-1. Menu.c.

/* QuickCase:W KNB Version 1.00 */


ttinclude "MENUl.h"

/************************************************************************ /
/* / *

/* Windows 3.0 Main Program Body */


/* * /

/************************************************************************ /

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/*********************************************************************** /
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/*********************************************************************** /

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

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;
}
}

/* create application's Main window */

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

Listing 4-1. (continued)

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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* End of WinMain */
/************************************************************************/
/* */
/* Main Window Procedure */
/* */
/************************************************************************/

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

Listing 4-1. (continued)

{
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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Application should draw on the client window using */


/* the GDI graphics and text functions. 'ps' the PAINTSTRUCT*/
/* returned by BeginPaint contains a rectangle to the */
/* area that must be repainted. */

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; ' /* End of WM_PAINT */

(continued)
128 Peter Norton’s QuickC for Windows

Listing 4-1. (continued)

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, and this window */
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 OL;
} /* End of WndProc */

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 */

void CwUnRegisterClasses(void)
{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));

UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */
Menus 129

However, there are a considerable number of details that we haven’t covered


yet, and we should take a moment to examine them before pressing on. In
particular, you may wonder how our menu is actually defined and where the
constant IDM F HELLO is set up, and how it gets passed to us. Let’s look at
the internal (i.e., programming) details of working with menus next.

Menu Programming Details


QuickCase:W has set up the constant IDM_F_HELLO for us in our file
menul.h, along with an identifier for the File menu itself:

/* QuickCase:W KNB Version 1.00 */


#include <windows.h>
#include <string.h>
#define IDM_FILE 1000 <—
#define IDM_F_HELL0 1050 <—

#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */


HWND hlnst;
HWND hWndMain;

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);

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"

MENUl MENU <—


BEGIN :
POPUP "File" :
BEGIN :
MENUITEM "Hello", IDM_F_HELLO :
END :

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));

/* load WNDCLASS with window's characteristics */


wndclass.style = CS_HREDRAW ! CS_VREDRAW ! CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra = 0;
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);
—> 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 */

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.

Adding an Exit Item to Our File Menu

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

Figure 4-6. Menul’s File Menu in QuickCase:W.


132 Peter Norton’s QuickC for Windows

QuickCase:W- (Untitled)1
File Edit Design Build Tools Options Help

PI «»
I Hello"
Exit

«»

Figure 4-7. Menul ’sFile Menu with Exit in QuickCase:W.

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

Figure 4-8. QuickCaseiW’s Edit Menu.


Menus 133

Now update the menul project by selecting Update in QuickCase:W’s Build


menu. When it is done, open the project menul.mak in the QuickC inte¬
grated development environment and also open menul.c from QuickC’s File
menu. We notice that a new case has been added to the WM_COMMAND
switch statement, IDM_F_EXIT:

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 */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

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)

PostQuitMessage(0);/* Quit the application*/


break;

default:
return DefWindowProc(hWnd, Message, wParam, lParam);

}
break; /* End of WM_COMMAND */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */

—> DestroyWindow(hWnd) ;

—> if (hWnd == hWndMain)

—> PostQuitMessage(0); /* Quit the application */


break;

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

Figure 4-9. Hello and Exit Menu Items in Menul.


Menus 135

development of menus already. However, we can do even better by adding


ALT key options and menu accelerators to our program, so let’s examine that
process next.

ALT Keys and Menu Accelerators


You’ve seen that many menu items have underlined characters in them; for
example, the x in Exit is usually underlined. That means that the user can
select that item simply by pressing ALT-X when that menu is open. In addi¬
tion, some menu items have menu accelerators that function whether or not the
menu is open; for example, the Insert menu item in QuickCase:W’s Edit
menu has the accelerator CTRL+I, as shown in Figure 4-8. We can also add
such conveniences to our own program.

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

Now bring menul.mak up in the QuickC integrated development environ¬


ment, rebuild menul.exe with the Rebuild All menu item of the Project
menu, and run it with the Go item in the Run menu. The result appears in
Figure 4-11 — as you can see, now we’ve added two ALT keys (i.e., ALT-H for
hello and ALT-X for Exit) and a menu accelerator (CTRL-H) to our program.
It was that easy.
136 Peter Norton’s QuickC for Windows

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 |

Figure 4-10. Designing ALT Keys and Menu Accelerators.

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

Figure 4-11. Menul with ALT Keys and a Menu Accelerator.

As we can see, there is a great amount of functionality that we can add to


menus — and we’re about to see more. In particular, we know that menu
items can be grayed out to indicate that they are inactive, and we know that
menu items may also appear with a check mark in front of their names to
indicate that some option (e.g., spellchecking) is active. Let’s add that to our
program now.

Graying Out and Checking Menu Items


For example, we can gray out the Hello menu item after the message, “Hello,
world.” has already been displayed. As we’ve already seen, we can indicate that
menu items like this should appear grayed out initially by clicking the Grayed
button in the Initial State box of QuickCase:W’s Insert Menu Item dialog box.
However, we can also gray an item from inside our program. Currently, our
IDM F HELLO case looks like this:

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.

Figure 4-12. Menul with Grayed Hello Item.


Menus 139

Figure 4-13. Menul with New Font Menu.

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:

SelectObj ect(hDC, GetStockObject(SYSTEM_FIXED_FONT));

The GetStockObj ect ( ) function retrieves predefined Windows objects, in¬


cluding such fonts as: ANSI_FIXED_FONT, ANSI_VAR_FONT, DEVICE_DE-
FAULT_FONT, OEM_FIXED_FONT, SYSTEM_FONT (the default Windows
font), and SYSTEMFIXEDFONT. Next, the SelectObject( ) function installs
the retrieved object in the device context.

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);

—> crOldTextColor = SetTextColor(hDC, GetBkColor(hDC));


—> TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));
—> SetTextColor(hDC, crOldTextColor);

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

SelectObj ect(hDC, GetStockObject(SYSTEM_FIXED_FONT));


TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world."));

—> hMenu = GetMenu(hWnd) ;


—> CheckMenuItern (hMenu, IDM_F_SYSTEMFIXEDFONT, MF_CHECKED) ;
ReleaseDC(hWnd, hDC) ;
break;

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

Figure 4-14. Menul Checked Font Menu

Listing 4-2. Menul.c with Grayable Item and Checkable Font Menu.

/* QuickCase:W KNB Version 1.00 */


#include "MENUl.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/ ***********************************************************************
/* HANDLE hlnstance; handle for this instance */

/* HANDLE hPrevInstance; handle for possible previous instances */


/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/*********************************************************************** j

MSG msg; /* MSG structure to store your messages */


int nRc ; /* return value from Register Classes */
Menus 143

Listing 4-2. (continued)

st rcpy(s zAppName, "MENU1");


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 CLASS, szString, \
sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return nRc;
}
}

/* create application's Main window */


hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Menu Input", /* Window's title */
WS_CAPTION ! /* Title and Min/Max */
W S_SY SMENU ! /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */
WS_MAXIMIZEBOX j /* Add maximize box */
WS_THICKFRAME ! /* thick sizeable frame */
WS_CLIPCHILDREN ! /* don't draw in child windows */
WS_OVERLAPPED,
CW_USEDEFAULT, 0, /* Use default X, Y */
CW_US EDEFAULT, 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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

(continued)
144 Peter Norton’s QuickC for Windows

Listing 4-2. (continued)

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* 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. */
/* */
/************************************************************************i

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

Listing 4-2. (continued)

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 */

case WM_MOVE: /* code for moving the window */

break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

(continued)
146 Peter Norton’s QuickC for Windows

Listing 4-2. (continued)

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window • */


DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

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));

/* 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);
Menus 147

Listing 4-2. (continued)

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 */

/************************************************************************ 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

Listing 4-3. (continued)

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.

Adding Menu Items at Run-Time


As an example program, we might write a small phone book application. We
might enter a person’s name simply by typing it and watching it appear in our
window as before:

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

Figure 4-15. Phone Application Template.

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

once, so we do it in the WM_CREATE case, storing the handle we need (of


type HMENU) in hFileMenu:

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

—> SetCaretPos(0, 2 * cyChar);


ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);

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

298-59 ! •*- User entering number means


bEnteringNumber = TRUE and characters
go into asNumberString[ ] [ ]
i

We can let each number be up to 20 characters, and handle up to 10 such


numbers, which means that asNumberString[ ][ ] will be declared like this:
static char asNumberString[10] [20]. We also need to keep track of the length
of each of these ten strings, and we can do that with an array of integers,
anNumberStrlen[ ], declared like this: static int anNumberStrlen[10];. In
other words, if the user wants phone number 0, we can find it stored in the
string asNumberString[0] [ ] and it’s anNumberStrlen[0] characters long:

asNumberString[ ] [ ]

asNumberString [0] anNumberStrlen[0] =8


Menus 153

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 */

—> 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) ;
}

On the other hand, if bEnteringNumber is false, the user is typing in a


person’s name, and we can keep track of those similarly with arrays named
asNameString[ ] [ ], which holds up to 10 names of 20 characters each, and
anNameStrlen[ ], which indicates how long each of the 10 names are:

asNameString[ ] [ ] asNumberString [ ] [ ]

asNameString[0]- “Albert” «- - anNameStrlen[0] = 6 “298-5995”


“George” “346-1237”
“Sam” “599-1007”
154 Peter Norton’s QuickC for Windows

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 */

—> asNameString[nNames] [anNameStrlen[nNames]] = (char) wParam;


: anNameStrlen[nNames]++;
: TextOut(hDC, 0, 0, (LPSTR) asNameString[nNames], \
: anNameStrlen[nNames]);
: SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
—> asNameString[nNames] , anNameStrlen[nNames]) , 0) ;
}
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
break;

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:

/* QuickCase:W KNB Version 1.00 */


#include <windows.h>
#include <string.h>
#define IDM_FILE 1000
#define IDM F ADDNAME 1050 <—

#define IDS_ERR_REGISTER_CLASS 1
#define IDS ERR CREATE WINDOW 2

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */

HWND hlnst;
HWND hWndMain;

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);

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

Note that we call DrawMenuBar( ) after using AppendMenu( ); Windows rec¬


ommends this procedure to make sure that our new menu item will be dis¬
played.

If we had passed the handle of our menu bar, hMenu, to AppendMenu( )


instead, we could have added a whole new popup menu.

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 second parameter in the InvalidateRect( ) call, NULL, indicates that we


want our entire window to be invalidated, and a nonzero third parameter
indicates that we want to repaint the background as well. The second parame¬
ter in the SendMessage ( ) call is the message we want to send, WM_PAINT,
the third parameter is the value we want passed as wParam (0), and the last
parameter is the value we want passed as IParam (0L). That’s it; at this point,
Menus 157

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 */

Note that if we couldn’t handle the menu message ourselves, we passed it on


to DefWindowProc ( ) as usual. That’s it; if we run the project, our phone book
appears. We can type a name, a tab, and then the person’s phone number as
in Figure 4-16. Then we add the person’s name to the File menu by selecting
the Add Name item, and we can keep going, entering name after name as
shown in Figure 4-17.

Figure 4-16. Our Phone Application.

Figure 4-17. Phone Application’s Filled File Menu.


Menus 159

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.

Listing 4-4. Phone.c.

/* QuickCase:W KNB Version 1.00 */


#include "PHONE.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/***********************************************************************/
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
***********************************************************************/

MSG msg; /* MSG structure to store your messages


*/
int nRc; /* return value from Register Classes

*/

st rcpy(s zAppName, "PHONE");


hlnst = hlnstance;
if(!hPrevInstance)

{
/* register window classes if first instance of application */
if ((nRc = nCwRegisterClasses()) == -1)
(continued)
160 Peter Norton’s QuickC for Windows

Listing 4-4. (continued)

{
/* 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 */
"Phone Book", /* Window's title */
WS_CAPTION 1
1 /* Title and Min/Max */
WS_S Y SMENU 1
1 /* Add system menu box */
WS_MINIMIZEBOX 1
1 /* Add minimize box */
WS_MAXIMIZEBOX 1
1 /* Add maximize box */
W S_THICKFRAME 1
1 /* thick sizeable frame */
WS_CLIPCHILDREN 1 /* 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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
Menus 161

Listing 4-4. (continued)

} /* 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

Listing 4-4. (continued)

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

Listing 4-4. (continued)

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 */
asNameString[nNames][anNameStrlen[nNames]] = \
(char) wParam;
anNameStrlen[nNames]++;
TextOut(hDC, 0, 0, (LPSTR) asNameString[nNames], \
anNameStrlen[nNames]);
SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
asNameString[nNames], anNameStrlen[nNames]) , 0);
}
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
break;

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

(continued)
164 Peter Norton’s QuickC for Windows

Listing 4-4. (continued)

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
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 */
/* 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) ) ;

/* load WNDCLASS with window's characteristics */


wndclass.style = CS_HREDRAW ! CS_VREDRAW ! CS_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;
Menus 165

Listing 4-4. (continued)

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 */

Listing 4-5. Phone.rc.


#include "PHONE.h"

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.

Creating a Paint Template Program


To start our paint application, open QuickCase:W and give the window there
the caption Paint. Next, add two menus to the menu bar named File and
Tools. In the File menu, place the items New (so that the user can clear the
current image and start over) and Exit, as shown in Figure 5-1.

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-1. Paint Program File Menu.

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

Figure 5-2. Paint Program Tools Menu.


Graphics and a Mouse-driven Paint Program 169

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:

Perhaps a better way of handling the IDMFEXIT case active is to send


ourselves a WM_CLOSE message just as we sent a WM_PAINT message in
the IDM_F_NEW case. That way, we wouldn’t be duplicating code through¬
out our program (i.e., if it was modified in one place, it might not be
modified in the other). However, here, the code is only three lines, so it
doesn’t make much difference.

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):

static BOOL bPoint = TRUE, bDraw, bLine, bRectangle, bEllipse, bFill;

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. */

—> 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. */
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 */

That function, CheckOnlyMenuItem( ), removes all check marks except the


one in front of the currently active tool. We can define it at the end of paint.c
like this:

void CheckOnlyMenuItem(HMENU hMenu, int IDM_T_TO_CHECK)

CheckMenuItem(hMenu, IDM_T_POINT, MF_UNCHECKED);


CheckMenuItem(hMenu, IDM_T_DRAW, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_LINE, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_RECTANGLE, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_ELLIPSE, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_FILL, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_TO_CHECK, MF_CHECKED);
}

We also add CheckOnlyMenuItem( )’s prototype to paint.h, the header file


where all prototypes go:

/* QuickCase:W KNB Version 1.00 */


#include <windows.h>
#include <string.h>
#define IDM_FILE 1000
#define IDM_F_NEW 1050
#define IDM_F_EXIT 1100
#define IDM_TOOLS 2000
#define IDM_T_POINT 2050
#define IDM_T_DRAW 2100
#define IDM_T_LINE 2150
#define IDM_T_RECTANGLE 2200
#define IDM_T_ELLIPSE 2250
#define IDM_T_FILL 2300

#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2
174 Peter Norton’s QuickC for Windows

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */


HWND hlnst;
HWND hWndMain;

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);
void CheckOnlyMenuItem(HMENU hMenu, int IDT_T_TO_CHECK); <—

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.

Setting Individual Pixels


We can set pixels in our applications with the Windows SetPixel( ) function;
all we have to pass to it is the handle to a device context (hDC), the location
of the point we want to set, and the color of the point. We can get a handle to
a device context with either BeginPaint( ) if part or all of the client area has
been declared invalid, or GetDC( ) if we want a handle to the whole client
area. In this case, we want GetDC( ). Let’s set the pixel when the user releases
the left mouse button; we can do that if we add a WM LBUTTONUP case,
check to make sure the bPoint flag is TRUE, and use SetPixel( ) like this:

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

Figure 5-3. Our Paint Program’s Point Item.

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.

Freehand Drawing in Our Paint Program


To let the user draw freehand, we should use the WM_MOUSEMOVE event.
This event is generated as the mouse moves across the screen; IParam
holds the current mouse cursor position (x = LOWORD (IParam),
176 Peter Norton’s QuickC for Windows

y = HIWORD(lParam)), and wParam is encoded bit by bit to indicate what


mouse buttons are down like this (MK stands for mouse key):

MK_LBUTTON Left button is down


MK_MBUTTON Middle button is down
MKRBUTTON Right button is down

That is, wParam is made up of the appropriate combination of these con¬


stants, ORed together. When the user wants to draw, they will hold the left
mouse button and move the mouse. We can check the left button and also
determine whether or not the Draw tool is active by checking bDraw like this:

case WM_MOUSEMOVE:
if((wParam && MK_LBUTTON) && bDraw){

Now we have to get a handle to the device context, which we do with


GetDC( ):

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;

Now, in the WM MOUSEMOVE event, we need to draw a line from


(xAnchor, yAnchor) to (LOWORD (lParam), HIWORD (lParam)). To draw
lines, we use the MoveTo( ) and LineTo( ) functions. We can only pass the
location of one point to LineTo( ), and it draws a line from the current position
to that point. To set the current position, we use MoveTo( ):

case WM_MOU S EMOVE:


if((wParam && MK_LBUTTON) && bDraw){
hDC = GetDC(hWnd);
MoveTo(hDC, xAnchor, yAnchor);

ReleaseDC(hWnd, hDC);
}

And then we can draw the line connecting the dots with LineTo( ):

case WM_MOU S EMOVE:


if((wParam && MK_LBUTTON) && bDraw){
178 Peter Norton’s QuickC for Windows

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));

-> xAnchor = LOWORD(lParam);

—> yAnchor = HIWORD(lParam);


ReleaseDC(hWnd, hDC);
}

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.

Figure 5-5. Freehand Drawing with Our Paint Program


Graphics and a Mouse-driven Paint Program 179

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;

Next, when the button goes back up in WMJLBUTTONUP, we can complete


the line. Since we want to draw a line from (xAnchor, yAnchor) to the new
location, which is encoded in lParam like this as (x, y): (LOWORD(lParam),
HIWORD (lParam)), we can draw our line 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);
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

Figure 5-6. Drawing Lines in Our Paint Program.

There are more options, here, however; we can draw in different colors as
well.

Selecting Colors and Pens

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

Or we can create our own pen with CreatePen( ):

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 Solid line


PS_DASH Dashed line
PS_DOT Dotted line
PS_DASHDOT Dash-dot line
PS_DASHDOTDOT Dash-dot-dot line
PS_NULL Null line (does not draw)
PS INSIDEFRAME Draw inside boundaries

PSSOLID
PSDASH
PSDOT
PSDASHDOT
PSDASHD0TD01
PSNULL
PS INSIDEFRAME

Figure 5-7. Windows Pen Styles.


182 Peter Norton’s QuickC for Windows

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.

We want a solid blue line, so we choose PS_SOLID. In addition, we want a pen


width of one pixel, so we have the first two parameters for CreatePen( ):

CreatePen(PS_SOLID, 1...);

Finally, we have to specify the color of the pen. To create a solid blue pen, we
can do this:

CreatePen(PS_SOLID, 1, RGB(0, 0, 255));

RGB(255, 0, 0) would have been all red, RGB(0, 255, 0) green.

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);

—> hPenOld = SelectObject(hDC, CreatePen(PS_SOLID, 1, RGB(0, 0, 255)));


MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));

}
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:

rectangle(hDC, ax, ay, bx, by);


184 Peter Norton’s QuickC for Windows

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.

Instead, we should draw figures and fill them transparently, so whatever is


behind them is preserved. To do that, we have to select a new brush. Just as
pens are used for drawing, so brushes are used for filling. And, just as there are
some stock pens we can use with GetStockObject( ), so there are stock
brushes: BLACK_BRUSH, DKGRAY_BRUSH, GRAY_BRUSH, HOL-
LOW_BRUSH LT_GRAY_BRUSH, NULLJBRUSH, WHITE_BRUSH. To
load, say, BLACK_BRUSH into the device context, and therefore to fill with
black, we would use this call:

hBackup = SelectObject(hDC, GetStockObject(BLACK_BRUSH));


Graphics and a Mouse-driven Paint Program 185

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:

hBackup = SelectObject(hDC, CreateSolidBrush(RGB(0, 255, 0));


186 Peter Norton’s QuickC for Windows

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:

hBackup = SelectObject(hDC, CreateHatchBrush(HS_DIAGCROSS, RGB(128, 128, 128));

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

Figure 5-9. Drawing Ellipses in Our Paint Program.

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.

Filling Figures with Color


It turns out to be easy to fill figures in in Windows; all we need to do is to use
the FloodFill( ) function. From the user’s point of view, that will work like this:
They position the mouse cursor inside a figure on the screen, click it, and the
figure fills with color. What’s happening here is that we get the location of the
mouse cursor from IParam in the WM LBUTTONUP case of WndProc( ),
and then we pass that set of coordinates on to FloodFill( ), along with a handle
to our device context and a bounding color.

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);

hBackup = SelectObject(hDC, GetStockObject(BLACK_BRUSH));

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

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);
hBackup = SelectObject(hDC, GetStockObject(BLACK_BRUSH));

—> FloodFill(hDC, LOWORD(lParam) , HIWORD(lParam) , RGB(0, 0, 0));

—> SelectObject(hDC, hBackup);


ReleaseDC(hWnd, hDC);
}
break;

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,

Figure 5-10. Filling with Color in Our Paint Program.


Graphics and a Mouse-driven Paint Program 191

or ellipses as they’re being sized, giving the user the illusion of “stretching” the
figure into shape. And we can do that too.

“Stretching” Graphics Figures


When the user moves the mouse, we get WM_MOUSEMOVE messages, and
we can give the appearance of stretching lines, rectangles, or ellipses as we do.
Let’s begin with lines. We’ll start off at the anchor point; when the user moves
the cursor, we’ll draw a line from the anchor point to the new mouse position.
When the cursor is moved again, we have to erase the old line and draw a new
one from the anchor point to the new cursor location. This will give the
impression that the user is stretching a line from the anchor point to the
mouse cursor’s position.

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:

1. Set anchor point in WM_LBUTTONDOWN case.

2. In WMMOUSEMOVE, draw a line from (xAnchor, yAnchor) to


(LOWORD (IParam), HIWORD(lParam)), and save (LOWORD(lParam),
HIWORD (IParam)) as (xold, yold).

3. In the next WMMOUSEMOVE event, erase the line from (xAnchor,


yAnchor) to (xold, yold), and draw a new line to the new coordinates,
(LOWORD (IParam), HIWORD (IParam)). Then update (xold, yold)
from (LOWORD (IParam), HIWORD (IParam)).

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

Operation Windows Draining Mode

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

Table 5-1. Windows Drawing Modes

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:

1. Set anchor point in WM LBUTTONDOWN case.

2. In WM_MOUSEMOVE, draw a line with a R2_NOT pen, inverting


screen pixels to make sure the line is visible, from (xAnchor, yAnchor)
to (LOWORD (IParam), HIWORD (IParam)), and save
(LOWORD(lParam), HIWORD(lParam)) as (xold, yold).

3. In the next WM_MOUSEMOVE event, erase the line from (xAnchor,


yAnchor) to (xold, yold), simply by drawing it again with a R2_NOT
pen, and draw a new R2_NOT line, inverting screen pixels, to the new
Graphics and a Mouse-driven Paint Program 193

coordinates, (LOWORD(lParam), HIWORD(lParam)). Then update


(xold, yold) from (LOWORD(lParam), HIWORD(lParam)).

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:

if((wParam && MK_LBUTTON) && bLine){


hDC = GetDC(hWnd);

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:

if((wParam && MK_LBUTTON) && bLine){


hDC = GetDC(hWnd);

—> nDrawMode = GetR0P2(hDC);

—> SetROP2(hDC, R2_N0T) ;

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);

—> xold = xAnchor;

—> yold = yAnchor;


break;

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:

if((wParam && MK_LBUTTON) && bLine){


hDC = GetDC(hWnd);
nDrawMode = GetR0P2(hDC);
SetR0P2(hDC, R2_N0T);

—> MoveTo(hDC, xAnchor, yAnchor);

—> LineTo(hDC, xold, yold);

—> MoveTo(hDC, xAnchor, yAnchor);

—> LineTo(hDC, LOWORD(lParam) , HIWORD(lParam));

ReleaseDC(hWnd, hDC);
}

All that remains now is to update (xold, yold) for the next time, and reset the
drawing mode:

case WM_MOUSEMOVE:

if((wParam && MK_LBUTTON) && bLine){


hDC = GetDC(hWnd);
nDrawMode = GetROP2(hDC);
SetR0P2(hDC, R2_N0T);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, xold, yold);
MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));

—> xold = LOWORD(lParam) ;

yold = HIWORD(lParam);

^ SetR0P2(hDC, nDrawMode);
ReleaseDC(hWnd, hDC);
}
Graphics and a Mouse-driven Paint Program 195

Figure 5-11. Stretching Lines in Our Paint Program.

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

LineTo(hDC, xold, yold);


MoveTo(hDC, xAnchor, yAnchor);
LineTo(hDC, LOWORD(lParam), HIWORD(lParam));
xold = LOWORD(lParam);
yold = HIWORD(lParam);
SetR0P2(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(lParam) , HIWORD(lParam));
SelectObject(hDC, hBackup);
xold = LOWORD(lParam);
yold = HIWORD(lParam);
SetR0P2(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;

And that’s all there is to it. The entire listing of paint.c appears in Listing 5-1.

Listing 5-1. Paint.c.

/* QuickCase:W KNB Version 1.00 */


#include "PAINT.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance,\


LPSTR IpszCmdLine, int nCmdShow)
{
Graphics and a Mouse-driven Paint Program 197

Listing 5-1. (continued)

/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

st rcpy(s zAppName, "PAINT") ;


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 */
"Paint", /* Window's title */
1
WS_CAPTION 1 /* Title and Min/Max */
1
WS_SY SMENU 1 /* Add system menu box */
1
WS_MINIMIZEBOX 1 /* Add minimize box */
11
WS_MAXIMIZEBOX // * Add maximize box */
1
W S_THICKFRAME 1 /* thick sizeable frame */
1
WS_CLIPCHILDREN 1 /* don't draw in child windows */
W S_OVER LA P P ED,
CW_US EDEFAULT, 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 */

(continued)
198 Peter Norton’s QuickC for Windows

Listing 5-1. (continued)

if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* 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; /* handle for the menu */

HBITMAP hBitmap=0; /* handle for bitmaps */


HDC hDC; /* handle for the display device */
PAINTSTRUCT ps; /* holds PAINT information */
HBRUSH hBrush, hBackup;
HPEN hPen;
int nRc=0; /* return code */
static BOOL bPoint = TRUE, bDraw, bLine, bRectangle, bEllipse, bFill;
static int xAnchor, yAnchor, xold, yold, nDrawMode;

switch (Message)
{
Graphics and a Mouse-driven Paint Program 199

Listing 5-1. (continued)

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

Listing 5-1. (continued)

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

Listing 5-1. (continued)

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

Listing 5-1. (continued)

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

Listing 5-1. (continued)

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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;
(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));

/* load WNDCLASS with window's characteristics */


wndclass.Style = CS_BYTEALIGNWINDOW; /* CS_HREDRAW ! CS_VREDRAW \
! CS_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 */

/************************************************************************ 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

Listing 5-1. (continued)

{
WNDCLASS wndclass; /* struct to define a window class */
memset(&wndclass, 0x00, sizeof(WNDCLASS));

UnregisterClass(szAppName, hlnst);
}

void CheckOnlyMenuItem(HMENU hMenu, int IDM_T_TO_CHECK)


{
CheckMenuItem(hMenu, IDM_T_POINT, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_DRAW, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_LINE, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_RECTANGLE, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_ELLIPSE, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_FILL, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_T_TO_CHECK, MF_CHECKED);
}

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( ).

Adding a Program Icon


The paint program we’ve developed in this chapter is about as close as we’ve
come to creating a polished Windows application, so, for that reason, let’s go
one step farther and give our program an icon that will appear in the Windows
Program Manager. We can design our own icon with the aid of the Image
Editor that comes with QuickC for Windows. Open it to show the Image
Editor window, as shown in Figure 5-12. Click on the New... item in the File
menu, and a dialog box will appear, listing the three types of images we can
create: cursors, icons, and bitmaps. Select icon and then click OK, bringing up
a new dialog box called New Icon Image as shown in Figure 5-13.
206 Peter Norton’s QuickC for Windows

Image Editor - (Untitled)


File Edit Image Brushsize Palette Options

Figure 5-12. The Image Editor.

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.

Image Editor - PAINT.ICO 03


Fil^^^dh^^ma2^^rushsiz^^a|ett^ Options
New Icon Image

Device type: 4-Plane Image width: 32 Pixels


Colors: 16 Image height: 32 Pixels
Standard Sizes
4-Plane 16-COLORS 32x32 pixels | ±|
4-Plane 16-COLORS 32x32 pixels
3-Plane 8-COLORS 32x32 pixels
Monochrome 2-COLORS 32x32 pixels
CGA 2-COLORS 32x16 pixels

Figure 5-13. New Icon Dialog Box.


Graphics and a Mouse-driven Paint Program 207

Image Editor - PAINT.ICO


File Edit Image Brushsize Palette Options

\
Left Right
® color wmnn
Q Screen I 1
O Inverse

Figure 5-14. Our Paint Application’s Icon.

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

Figure 5-15. Our Paint Program’s Icon in a Group.


.«• \
Dialog Boxes: Buttons and
Textboxes

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:

int MessageBox(hWnd, lpText, lpCaption, wType);

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;

FFi le Message Box Example

Message Box.

Figure 6-1. Message Box Example.


Dialog Boxes: Buttons and Textboxes 211

And this is where we’ll put our MessageBox( ) call:

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.

Two more important MB types are MB APPLMODAL and MB SYS-


TEMMODAL. We say a dialog box is modal when we expect the user to deal
with it before continuing with the rest of the program (as opposed to non-
modal dialog boxes that can appear and operate side by side with other win¬
dows). An MB_APPLMODAL message box is one that is modal on the
application level; that is, before continuing to use the application, the user
must finish with dialog box. Clicking any other windows belonging to the
application results in a beep. MB_SYSTEMMODAL message boxes, on the
other hand, don’t even allow the user to switch to other applications before
closing the message box.

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

MB_ABORTRETRYIGNORE Abort, Retry, Ignore buttons


MB_APPLMODAL Modal on application level
MB_DEFBUTTONl First button is default
MB_DEFBUTTON2 Second button is default
MB_DEFBUTT ON 3 Third button is default
MBJCONASTERISK Same as MB_ICONINFORMATION
»

MBJCONEXCLAMATION Include exclamation point icon


MBJCONHAND Same as MBJCONSTOP
MBICONINFORMATION Include circle i icon
MBICONQUESTION Include question mark icon
MBICONSTOP Include stop sign icon
MB_OK OK button
MB_OKCANCEL OK and Cancel buttons
MB_RETRYCANCEL Retry and Cancel buttons
MB_SYSTEMMODAL Modal on system level
MB_YESNO Yes and No buttons
MB_YESNOCANCEL Yes, No, Cancel buttons

Table 6-1. Message Box Types

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):

IDABORT User clicked Abort Button


IDCANCEL User clicked Cancel Button
IDIGNORE User clicked Ignore Button
IDNO User clicked No Button
IDOK User clicked Ok Button
IDRETRY User clicked Retry Button
IDYES User clicked Yes Button

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 Example T| A|


File

Message Box

This is a message box.

OK Cancel
mmm

Figure 6-2. Our Message Box’s Appearance.

As helpful as MessageBox( ) is, we can go much farther when we design our


own dialog boxes, as we’ll see. For example, what if we wanted to add a text
box to the message box in Figure 6-2? In that case, we’d have to design our
own dialog box. Fortunately, the Dialog Editor makes that an easy job, so let’s
turn to that at once.

Designing Dialog Boxes


Let’s say that our goal is simply to set up a dialog box much like the one in
Figure 6-2, except that we want to add a text box and a button marked Push
Me. When the user clicks this button, we can put the phrase “Hello, world.”
into the text box. We’ll design our dialog box with the Dialog Editor, integrate
it into a program which we might name mydlg.mak with QuickCaseiW, then
build mydlg.exe with QuickC for Windows.

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

Figure 6-3. The Dialog Editor.

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.

Text tool - A ab -Text Box tool

Group Box tool- n□ - Push Button tool

Check Box tool- ® - Radio Button tool


Combo Box tool- '^8 Er v - List Box tool

Horizontal Scroll Bar tool - M*l T| - Vertical Scroll bar tool



Frame tool - □ ■ - Rectangle tool

Icon tool - <2? □ - Custom tool

Figure 6-4. The Dialog Editor’s Toolbox.


Dialog Boxes: Buttons and Textboxes 215

Figure 6-5. New Dialog Box Template.

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.

In addition, we can customize any of the controls we add to a dialog box by


double clicking the control itself when we design our dialog box. For exam-
216 Peter Norton’s QuickC for Windows

Dialog Editor - (Untitled)*, (Untitled)


| File Edit Arrange Options Help
(7, 1G)
cy: 14
cx: 40 Symbol:
(47, 30) Text: PushJ
a 101

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.

Dialog Editor - (Untitled)*, (Untitled)


1 File Edit Arrange Options Help
(7,1G)
cy: 14
cx: 40 Symbol:
(47, 30) Text: Push Me
ia 101

- 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

Figure 6-8. Button Customization Box.

pie, double clicking the button Push Me now brings up the dialog box shown
in Figure 6-8, showing the options available for buttons.

Of the button options, the Default option is particularly worth noticing. If


set, this makes the button into the default control for the dialog box (i.e., it
has the focus) when the dialog box appears. It’s often customary to make
an OK button the default so the user has only to press Enter.

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

Resource Memory Flags Optional Controls


I I iPreloadj EH Pure [X] Caption [Xi System Menu
[Xj Moveable EH Discardable HU Min Button I I Vert. Scroll Bar
EH Max Button I I Horz. Scroll B ar
Font Face Name: Pt. Size:
Helv
a 8 a " Dialog Styles
[X] Popup I I Absolute Align
Optional Class:
□ Child I I System Modal
Frame Style — I I Clip Siblings I I No Idle Message
X] Border EH Modal i I Clip Children EH Visible
X] Dialog I I Sizing (Thick) I I Local Edit I I Disabled

OK Cancel Help

SC/
El

Figure 6-9. Dialog Box Options.

:-r-; **

0mm Dialog Editor - DIALOG.RES*, (Untitled)


| File Edit Arrange Options Help
1 (68, 37) cx: 151 Dig. Sym.:
is 100
| cy: 86 (219, 123) Caption: Dialog Box

■ ■ -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

Figure 6-11. Configuring an OK button.

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

100 DIALOG 90, 39, 151, 86


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Dialog Box"
FONT 8, "Helv"
BEGIN
EDITTEXT 101, 76, 20, 55, 15, ES._AUTOHSCROLL
PUSHBUTTON
ish Me", 102, 15, 20, 40, 14
PUSHBUTTON "OK" , IDOK, 13, 58, 40, 14
PUSHBUTTON "Cancel" , IDCANCEL, 85, 57, 40, 14
END

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

«»

Figure 6-12. Dialog Box Example’s File Menu.


Dialog Boxes: Buttons and Textboxes 221

Insert Menu Item


Style
® String (MYDLG.WIN)*
O Separator i Tools Options Help
O Bitmap...
x Program
Name: Dialog Box...

Accelerator:

Initial State

® Active O' Checked

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;

Incprf Upnn iff

Dialog Box

File Name: Directories: OK


dialog, dig c:\qcwin\bin
calc, dig Cancel
dialog, dl
dialogs, dig
dlgs3.dlg
Help j
menuchek.dlg
notepad, dig
testing, dig

List Files of Type: Drives:


Dialog Script (z.dlg) jjjj c: dos400
a
Attributes
<•) Modal 3 Centered

O Modeless

Figure 6-14. Linking Dialog, dig to Our File Menu.


Dialog Boxes: Buttons and Textboxes 223

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);

—> nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \

—» 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. */
/* */
/************************************************************************/

BOOL FAR PASCAL DIALOGMsgProc(HWND hWndDlg, WORD Message, WORD wParam,\


LONG 1Param)
{

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

/* and dismiss the dialog window returning FALSE */

—> EndDialog(hWndDlg, FALSE);


break;

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:

case WM_C OMMAND:


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 */

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:

BOOL FAR PASCAL DIALOGMsgProc(HWND hWndDlg, WORD Message, WORD wParam,\


LONG lParam)

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

—> 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;

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

#include "DIALOG.DLG" <—

STRINGTABLE
BEGIN
IDS_ERR_CREATE_WINDOW, "Window creation failed!"
IDS_ERR_REGISTER_CLASS, "Error registering window class
END

In other words, the specification of the dialog box is considered a resource by


itself, which is why it is included in the resource file.
228 Peter Norton’s QuickC for Windows

Dialog Box Program Eid.


Fi Dialog Box

iPush Me! f Hello, world.

[ 1 Cancel
6

Figure 6-15. “Hello, world.” Dialog Box Example.

Listing 6-1. Mydlg.c.

/* QuickCase:W KNB Version 1.00 */


#include "MYDLG.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/*********************************************************************** /
/* HANDLE, hlnstance; handle for this instance -k
/
■k
/* HANDLE hPrevInstance; handle for possible previous instances /
/* LPSTR IpszCmdLine; long pointer to exec command line *
/
/* int nCmdShow; Show code for main window display *
/
'k'k'kkkkkkk'k'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk'kkkkkkkkkkkkkkkkkkkk
/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

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

Listing 6-1. (continued)

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */
"Dialog Box Program", /* Window's title */
WS_CAPTION I /* Title and Min/Max */
WS_SYSMENU ! /* Add system menu box */
WS_MINIMIZEBOX j /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
WS_THICKFRAME 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)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* 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 */

(continued)
230 Peter Norton's QuickC for Windows

Listing 6-1. (continued)

/ * 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 */

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

Listing 6-1. (continued)

}
break; /* End of WM_COMMAND */

case WM_CREATE:
break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */

/* */

(continued)
232 Peter Norton’s QuickC for Windows

Listing 6-1. (continued)

/* 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 DIALOGMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG 1Param)
{

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

Listing 6-1. (continued)

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));

/* load WNDCLASS with window's characteristics /


wndclass.style = CS_HREDRAW I 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)
234 Peter Norton's QuickC for Windows

Listing 6-1. (continued)

void cwCenter(hWnd, top)


HWND hWnd;
int top;
{
POINT pt;
RECT swp;
RECT rParent;
int iwidth;
int iheight;

/* get the rectangles for the parent and the child */


GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, krParent);

/* calculate the height and width for MoveWindow */


iwidth = swp.right - swp.left;
iheight = swp.bottom - swp.top;

/* find the center point and convert to screen coordinates */


pt.x = (rParent.right - rParent.left) / 2;
pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point */


pt.x = pt.x - (iwidth / 2);
pt.y = pt.y - (iheight / 2);

/* top will adjust the window position, up or down */


if(top)
pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}

/************************************************************************/

/* 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: Buttons and Textboxes 235

Listing 6-1. (continued)

WNDCLASS wndclass; /* struct to define a window class */


memset(&wndclass, 0x00, sizeof(WNDCLASS));

UnregisterClass(szAppName, hlnst);
} /* End of CwUnRegisterClasses */

Listing 6-2. Mydlg.h.


/* QuickCase:W KNB Version 1.00 */
#include <windows.h>
#include <string.h>
#define IDM_FILE 1000
#define IDM_F_DIALOGBOX 1050
#define IDM_F_EXIT 1100

ttdefine IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */


HWND hlnst;
HWND hWndMain;

void cwCenter(HWND, int);

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


BOOL FAR PASCAL DIALOGMsgProc(HWND, WORD, WORD, LONG);
int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);

Listing 6-3. Mydlg.rc.


#include "MYDLG.h"

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

Listing 6-3. (continued)

#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).

2. Add the dialog box to a program in QuickCase:W using the Link To


box, and Configure Link..., specifying the .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.

At this point, we’ve arranged our controls like this:

+- text box

text box

button
Dialog Boxes: Buttons and Textboxes 237

Dialog Editor - CALC.RES, (Untitled]


File Edit Arrange Options Help
(68, 26) cx: 115 Dig. Sym.:
3 100
cy: 114 (183, 140) Caption: Calculator

Calculator

A ab

□ □

2*



Figure 6-16. Calculator Template with Two Text Boxes.

Figure 6-17. Nearly Complete Calculator Template.


238 Peter Norton’s QuickC for Windows

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):

100 DIALOG 68, 26, 115, 114


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Calculator"
FONT 8, "Helv"
Dialog Boxes: Buttons and Textboxes 239

Dialog Editor - CALC.RES, (Untitled)


File Edit Arrange Options Help
(41,90) cx: 27 Symbol:
1 105
cy: 11 (68,101) Text: 0

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

BOOL FAR PASCAL CALCMsgProc(HWND hWndDlg, WORD Message, WORD wParam,\


LONG 1Param)
{

char szOpl[5], szOp2[5], szResult[6];


int nOpl, nOp2;

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 102: /* Edit Control */


break;

case 103: /* Button text: "=" */


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

List Files of Type: Drives:


Dialog Script (z.dlg) c: dos400
a
Attributes

<S> Modal ^ Centered

O Modeless

j j ~~

Figure 6-19. Configuring the Calculator’s Link.

display them in the answer box. The controls we have, and their IDs, are set up
like this:

12 101

102

103

104

Therefore, we want to concentrate on case 103 in CALCMsgProc( ), the but¬


ton marked

case 103: /* Button text: tl _ I!


*/
break;
242 Peter Norton’s QuickC for Windows

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:

char szOpl[5], szOp2 [5] ;

We can use SetDlgItemText( )’s counterpart, GetDlgItemText( ) to actually


get the text now in the text boxes and fill these strings, like this:

case 103: /* Button text: "=" */


GetDlgltemText(hWndDlg, 101, (LPSTR) szOpl, 5);
GetDlgltemText(hWndDlg, 102, (LPSTR) szOp2, 5);

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:

char szOpl[5], szOp2[5];


int nOpl, nOp2;

Then we use atoi( ) like this:

• case 103: /* Button text: "=" */


GetDlgltemText(hWndDlg, 101, (LPSTR) szOpl, 5);
GetDlgltemText(hWndDlg, 102, (LPSTR) szOp2, 5);
—> nOpl = atoi(szOpl);
—> nOp2 = atoi(szOp2);

break;

In this case, we’re using GetDlgItemText( ) followed by atoi( ) to get the


number from the text boxes in case you want to adapt the technique to
more complex values like floating point numbers. However, if the number
really is an integer, you can combine these two steps into one with
GetDlgItemInt( ) instead of GetDlgltemText( ).

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:

char szOpl[5], szOp2[5], szResult[6]; <—


int nOpl, nOp2;
Dialog Boxes: Buttons and Textboxes 243

To fill szResult, we can use the C function itoa( ):

case 103: /* Button text: "=" */


GetDlgltemText(hWndDlg, 101, (LPSTR) szOpl, 5);
GetDlgltemText(hWndDlg, 102, (LPSTR) szOp2, 5);
nOpl = atoi(szOpl);
nOp2 = atoi(szOp2);

—> itoa(nOpl+nOp2, szResult, 10);

break;

It only remains to display szResult in the answer text field (whose ID is 105),
and we can do that like this:

case 103: /* Button text: "=" */


GetDlgltemText(hWndDlg, 101, (LPSTR) szOpl, 5);
GetDlgltemText(hWndDlg, 102, (LPSTR) szOp2, 5);
nOpl = atoi(szOpl);
nOp2 = atoi(szOp2);
itoa(nOpl+nOp2, szResult, 10);
—> SetDlgltemText(hWndDlg, 105, (LPSTR) szResult);
break;

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:

/* QuickCase:W KNB Version 1.00 */


#include <windows.h>
#include <string.h>
#include <stdlib.h> <—
#define IDM_FILE 1000
#define IDM_F_CALCULATOR 1050
#define IDM_F_EXIT 1150

#define IDS_ERR_REGISTER_CLASS 1
#define IDS_ERR_CREATE_WINDOW 2

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */

HWND hlnst;
244 Peter Norton’s QuickC for Windows

HWND hWndMain;

void cwCenter(HWND, int);

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


BOOL FAR PASCAL CALCMsgProc(HWND, WORD, WORD, LONG);
int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);

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

Figure 6-20. Our Calculator Example.

Listing 6-4 Mycalc.c


/* QuickCase:W KNB Version 1.00 */
#include "MYCALC.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
Dialog Boxes: Buttons and Textboxes 245

Listing 6-4. (continued)

/
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */

/* LPSTR IpszCmdLine; long pointer to exec command line */

/* int nCmdShow; Show code for main window display */

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

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

Listing 6-4. (continued)

Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));


MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* 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)
{
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

Listing 6-4. (continued)

/* Menu Item Named "Calculator..." here. */

{
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:

break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */

break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */

/* Obtain a handle to the device context */

/* BeginPaint will sends WM_ERASEBKGND if appropriate */

memset(&ps, 0x00, sizeof(PAINTSTRUCT));


hDC = BeginPaint(hWnd, &ps);

/* included in case the background is not a pure color */

SetBkMode(hDC, TRANSPARENT);

(continued)
248 Peter Norton’s QuickC for Windows

Listing 6-4. (continued)

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 CALCMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG lParam)
{

char szOpl[5], szOp2[5], szResul.t[6];


int nOpl, nOp2 ;

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

Listing 6-4. (continued)

PostMessage(hWndDlg, WM_COMMAND, IDCANCEL, OL);


break; /* End of WM_CLOSE */

case WM_COMMAND:
switch(wParam)
{
case 101: /* Edit Control */
break;

case 102: /* Edit Control */


break;

case 103: /* Button text: "=" */

GetDlgltemText(hWndDlg, 101, (LPSTR) szOpl, 5);


GetDlgltemText(hWndDlg, 102, (LPSTR) szOp2, 5);
nOpl = atoi(szOpl);
nOp2 = atoi(szOp2);
itoa(nOpl+nOp2, szResult, 10);
SetDlgltemText(hWndDlg, 105, (LPSTR) szResult);
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 */

/*
***********************************************************************/
/* */

/* 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)
250 Peter Norton’s QuickC for Windows

Listing 6-4. (continued)

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 ! CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra = 0;
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);
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 */
/*
/************************************************************************/

void cwCenter(hWnd, top)


HWND hWnd;
int top;

POINT
RECT
RECT rParent;
int iwidth;
int iheight;

/* get the rectangles for the parent and the child


GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, &rParent);

/* calculate the height and width for MoveWindow */


iwidth = swp.right - swp.left;
iheight = swp.bottom - swp.top;
Dialog Boxes: Buttons and Textboxes 251

Listing 6-4. (continued)

/* find the center point and convert to screen coordinates */


pt.x = (rParent.right - rParent.left) / 2;
pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point */


pt.x = pt.x - (iwidth / 2);
Pt.y = pt.y - (iheight / 2);

/* top will adjust the window position, up or down */


if(top)
pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}

/★★★★★★★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 */

Listing 6-5. Mycalc.rc.


#include "MYCALC.h"

MYCALC MENU
BEGIN
POPUP "File"
BEGIN
MENUITEM "Calculator...", IDM_F_CALCULATOR

(continued)
252 Peter Norton’s QuickC for Windows

Listing 6-5. (continued)

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

Figure 6-21. Starting Our Notepad Template.

File Edit Edit Field Styles


(5, 4)
cy: 89 (1
'Alignment
® I Left; O Center O Right
a
Edit Field Styles

*
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

Figure 6-22. The Edit Field Styles Dialog Box.


254 Peter Norton’s QuickC for Windows

Figure 6-23. Our Completed Notepad Template.

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):

SendDlgltemMessage(hWndDlg, 101, WM_CUT, 0, 0L); //Cut text


SendDlgltemMessage(hWndDlg, 101, WM_COPY, 0, 0L) ; //Copy text
SendDlgltemMessage(hWndDlg, 101, WM_CLEAR, 0, 0L); //Clear text
SendDlgltemMessage(hWndDlg, 101, WM_PASTE, 0, 0L) ; //Paste text

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 102: /* Button text: "Cut" */

—» SendDlgltemMessage(hWndDlg, 101, WM_CUT, 0, 0L);


break;

case 103: /* Button text: "Paste" */


—> SendDlgltemMessage(hWndDlg, 101, WM_PASTE, 0, 0L) ;
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

This is a test. For the next sixty words,


this notepad will conduct a test,
showing automatic word wrap,
backspacing, and many other built-in
a
features of multi-line text boxes...|

a
Cut [ Paste

Figure 6-24. Our Notepad Program at Work.

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):

lSelection = SendDlgltemMessage(hWndDlg, 101, EM_GETSEL, 0, 0L);


nStartPosition = LOWORD(lSelection) ;
nEndPosition = HIWORD(lSelection) - 1;

Or, we can select the start and end positions of the selected text ourselves with
EM_SETSEL and the MAKELONG( ) macro:

SendDlgltemMessage(hWndDlg, 101, EM_SETSEL, 0, MAKELONG(nStart, nEnd));


Dialog Boxes: Buttons and Textboxes 257

EM_CANDO Can undo occur?


EM_EMPTYUNDOBUFFER Clear undo buffer
EM_FMTLINES Add/remove end-of-line chars
EM_GETHANDLE Get handle to data buffer of text
EM_GETLINE Gets a line
EM_GETLINECOUNT Get number of text lines
EM_GETMODIFY Return modify flag
EM_GETRECT Retrieve formatting rectangle
EM_GETSEL Get start and end selected chars
EM_LIMITTEXT Limits length (bytes) user can enter
EM_LINEFROMCHAR Get line number of a char
EM_LINEINDEX Get # of positions before first char on line
EM_LINELEN GTH Get length of a line
EM_LINESCROLL Scroll lines by indicated number
EM_REPLACESEL Replace selection with new text
EM_SETHANDLE Set up text buffer
EM_SETMODIFY Set modify flag
EM_SETPASSWORDCHAR Sets char displayed in password text boxes
EM_SETRECT Sets formatting rectangle
EM_SETRECTNP Set formatting rectangle, do not repaint
EM_SETSEL Set selection
EM_SETTABSTOPS Set tab stops
EM_SETWORDBREAK Let application do work break
EM_UNDO Undo last edit

Table 6-2. Text Box Command Messages.

Or, we can find the number of lines in a multi-line text box (like ours) with
EM_GETLINECOUNT:

nLines = SendDlgltemMessage(hWndDlg, 101, EM_GETLINECOUNT, 0, 0L);

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

Listing 6-6. Mypad.c.


/* QuickCaserW KNB Version 1.00 */
#include "MYPAD.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/***********************************************************************/
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages


*/
int nRc; /* return value from Register Classes
*/

st rcpy(s zAppName, "MYPAD");


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 */
"Notepad Example", /* Window's title */
WS_CAPTION ! /* Title and Min/Max */
W S_SY SMENU ! /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
W S_THICKFRAME ! /* 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 */
Dialog Boxes: Buttons and Textboxes 259

Listing 6-6. (continued)

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;

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */

{
TranslateMessage(&msg);
DispatchMessage(&msg);

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* 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)

{
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 */

(continued)
260 Peter Norton’s QuickC for Windows

Listing 6-6. (continued)

switch (Message)
{
case WM_COMMAND:
/* The Windows messages for action bar and pulldown menu items */
/* are processed here. */
switch (wParam)
{
case IDM F NOTEPAD:
— »

/* Place User Code to respond to the */

/* Menu Item Named "Notepad..." here. */

{
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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


Dialog Boxes: Buttons and Textboxes 261

Listing 6-6. (continued)

/* Obtain a handle to the device context */


/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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, 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. */
/* */
/************************************************************************/

BOOL FAR PASCAL NOTEPADMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG IParam)
{

switch(Message)
{

(continued)
262 Peter Norton’s QuickC for Windows

Listing 6-6. (continued)

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;

case 102: /* Button text: "Cut" */


SendDlgltemMessage(hWndDlg, 101, WM_CUT, 0, 0L);
break;
case 103: /* Button text: "Paste" */
SendDlgltemMessage(hWndDlg, 101, WM_PASTE, 0, 0L);
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 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

Listing 6-6. (continued)

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 I 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 */
/* */
/************************************************************************/

void cwCenter(hWnd, top)


HWND hWnd;
int top;
{
POINT pt ;
RECT swp;
RECT rParent;
int iwidth;
int iheight;

/* get the rectangles for the parent and the child */

GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, SrParent);

(continued)
264 Peter Norton’s Quick C for Windows

/* calculate the height and width for MoveWindow */


iwidth = swp.right - swp.left;
iheight = swp.bottom - swp.top;

/* find the center point and convert to screen coordinates */


pt.x = (rParent.right - rParent.left) / 2;
pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point . */


pt.x = pt.x - (iwidth / 2);
pt.y = pt.y - (iheight / 2);

/* top will adjust the window position, up or down */


if (top)
pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}

/************************************************************************/

/* 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

Listing 6-7. Notepad.dlg.


100 DIALOG 54, 28, 160, 114
STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Notepad"
FONT 8, "Helv"
BEGIN
EDITTEXT 101, 5, 4, 151, 89, E S_MULTILINE ! ES_AUTOHSCROLL !
WS_VSCROLL
PUSHBUTTON "Cut", 102, 34, 98, 40, 14
PUSHBUTTON "Paste", 103, 96, 98, 40, 14
END

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

are used when we have a number of predetermined options we want to pres¬


ent to the user. In this chapter, we’ll update our phone book application from
Chapter 4 so that we can display the list of names in a list box, not a menu.
Let’s start at once with scroll bars and put together our control panel applica¬
tion.

A Control Panel Example


»

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

Figure 7-1. Scroll Bars in Our Control Panel.

Figure 7-2. Labeled Scroll Bars.


270 Peter Norton’s QuickC for Windows

Figure 7-3. Our Completed Control Panel Template.

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:

100 DIALOG 61, 27, 160, 100


STYLE DS_MODALFRAME | WS_POPUP j WS_VISIBLE ! WS_CAPTION i WS_SYSMENU
CAPTION "Control Panel"
FONT 8, "Helv"
BEGIN
SCROLLBAR 101, 13, 18, 10, 66, SBS_ VERT
SCROLLBAR 102, 51, 18, 10, 66, SBS_ VERT
SCROLLBAR 103, 91, 18, 10, 66, SBS_ VERT
LTEXT "Select New Window Color: ", 104, 1, 91, 8
LTEXT "Red", 105, 11, 88, 20, 8
LTEXT "Green", 106, 46, 88, 20, 8

LTEXT
CO
CO

"Blue", 107, 87, 20, 8


PUSHBUTTON "OK", IDOK, 115, 27, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 115, 51, 40, 14
END

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

File Name: Directories: OK


jpanel.dlg c:\qcwin\bin
i i
calc, dig Cancel
dialog, dig
dialogs, dig
dlgs3.dlg Help
menuchek.dlg
notepad, dig
panel, dig
testing, dig

List Files of .Type: Drives:


Dialog Script p.dlg) ij c: dos400
a
Attributes
<S> Modal 3 Centered

O Modeless

urv cancer neip

Figure 7-4. Linking Our Control Panel In.

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

/* one of the dialog box's buttons, entry fields, or controls. */


/* */
/************************************************************************/

BOOL FAR PASCAL PANELMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG lParam)
{
static int nRed, nGreen, nBlue, nID;

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

SBLINEUP Left/top arrow box or left/up arrow key


SB_LINEDOWN Right/bottom arrow box or right/bottom arrow key
SB_PAGEUP Bar above/left of thumb or PgUp key
SB_PAGEDOWN Bar below/right of thumb or PgDn key
SBTHUMBPOSITION New thumb position (after tracking is done)
SB_THUMBTRACK Scroll bar’s thumb was moved
SBTOP User pressed Home key (no mouse equivalent)
SB_BOTTOM User pressed End key (no mouse equivalent)
SB ENDSCROLL Scroll event is over (mouse button up)

If we get a SB_THUMBPOSITION or a SB_THUMBTRACK event, then the


user is moving the scroll bar’s thumb, and Windows changes the setting of the
scroll bar to a new value. It’s still up to us, however, to place the thumb at its
new position. On the other hand, if we get any other message, Windows does
not change the setting of the scroll bar; that is, if the user clicks the arrow
buttons at the left/top or right/bottom of the scroll bar, or if they click the
scroll bar itself to the left/above or right/below of the scroll bar, we’re respon¬
sible for determining how much the thumb moves, and we do that with the
SetScrollPos( ) function later.

In our case, the message that interests us in our example is the


WM_VSCROLL message with wParam set equal to SB_THUMBPOSITION,
which we receive after the user has moved the thumb to a new position (it’s
easy to add support for the other scroll bar messages after you see how to
handle SB_THUMBPOSITION). We can add that case to the switch statement
now in PANELMsgProc ( ):

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:

static int nRed, nGreen, nBlue, nID;

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

—> SetScrollRange(GetDlgltem(hWndDlg, 103), SB_CTL, 0, 255, TRUE);


—> /* initialize working variables */
break; /* End of WM_INITDIALOG */

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:

—> SetScrollRange(GetDlgltem(hWndDlg, 101), SB_CTL, 0, 255, TRUE);

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:

- -»- Thumb here means SB setting = minimum

Thumb here means SB setting = minimum


276 Peter Norton's QuickC for Windows

Finally, we pass a value of TRUE to SetScrollRange ( ) to indicate that we want


it to be redrawn to reflect any changes we make to its setting (otherwise,
Windows will not redraw the position of the thumb).

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 ( ).

However, if we don’t update the thumb position ourselves with


SetScrollPos( ), it turns out that Windows will automatically move the thumb
back to its original position after the user releases it. To prevent that, we use
SetScrollPos( ) like this:

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

static int nRed, nGreen, nBlue, nID;


switch(Message)
{
case WM_INITDIALOG:
cwCenter(hWndDlg, 0);
// Set scrollranges
SetScrollRange(GetDlgltem(hWndDlg, 101) , SB_CTL, , 255, TRUE);
SetScrollRange(GetDlgltem(hWndDlg, 102) , SB_CTL, , 255, TRUE);
SetScrollRange(GetDlgltem(hWndDlg, 103) , SB_CTL, , 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){
nBlue = LOWORD(lParam);

—> SetScrollPos(GetDlgltem(hWndDlg, 103), SB_CTL, nBlue, TRUE);


}
break;

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:

The GetClassWord( ) function is often used to retrieve settings of various


words for a class as were orignally passed to RegisterClass( ). The
information that you can get includes: GCW_HBRBACKGROUND,
GCW_HCURSOR, GCW_HICON, GCW_HMODULE, GCW_CBW-
NDEXTRA, GCW_CBCLSEXTRA, GCL_WNDPROC, and GCW_STYLE.
Dialog Boxes: Scroll Bars and Listboxes 279

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);

—> EndDialog(hWndDlg, TRUE) ;


break;

And that’s it; here is how the bulk of our function PANELMsgProc ( ) looks:

static int nRed, nGreen, nBlue, nID;

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

Red Green Blue

Figure 7-5. Our Functioning Control Panel.

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.

Figure 7-6. Our Control Panel at Work.

Listing 7-1. Mypanel.c.


/* QuickCaserW KNB Version 1.00 */
#include "MYPANEL.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
Dialog Boxes: Scroll Bars and Listboxes 283

Listing 7-1. (continued)

{
/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

st rcpy(s zAppName, "MYPANEL");


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(
szAppName, /* Window class name */
"Control Panel Example", /* Window's title */
WS_CAPTION I /* Title and Min/Max */
WS_SYSMENU ! /* Add system menu box */
WS_MINIMIZEBOX I /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
WS_THICKFRAME ! /* 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 */

(continued)
284 Peter Norton’s QuickC for Windows

Listing 7-1. (continued)

if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */

{
TranslateMessage(&msg);
DispatchMessage(&msg);

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* 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)
{
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

Listing 7-1. (continued)

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:

break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color (continued)


286 Peter Nortons QuickC for Windows

Listing 7-1. (continued)

SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, .then, this window */
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, 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. */
/* */
/************************************************************************/

BOOL FAR PASCAL PANELMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG IParam)
{
static int nRed, nGreen, nBlue, nID;

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

Listing 7-1. (continued)

/* initialize working variables */


break; /* End of WM_INITDIALOG */

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

Listing 7-1. (continued)

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));

/* load WNDCLASS with window's characteristics */


wndclass.style = CS_HREDRAW i CS_VREDRAW ! CS_BYTEALIGNWINDOW;
wndclass.lpfnWndProc = WndProc;
/* Extra storage for Class and Window objects */
wndclass.cbClsExtra = 0;
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);
wndclass.IpszMenuName = szAppName; /* Menu Name is App Name */
wndclass.IpszClassName = szAppName; /* Class Name is App Name */
if(!RegisterClass(&wndclass))
return -1;
Dialog Boxes: Scroll Bars and Listboxes 289

Listing 7-1. (continued)

return(0);
} /* End of nCwRegisterClasses */

/************************************************************************/

/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/* */
/************************************************************************/

void cwCenter(hWnd, top)


HWND hWnd;
int top;
{
POINT pt ;
RECT swp ;
RECT rParent;
int iwidth;
int iheight;

/* get the rectangles for the parent and the child */


GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, &rParent);

/* calculate the height and width for MoveWindow */


iwidth = swp.right - swp.left;
iheight = swp.bottom - swp.top;

/* find the center point and convert to screen coordinates */


pt.x = (rParent.right - rParent.left) / 2;
pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point */


pt.x = pt.x - (iwidth / 2);
Pt.y - pt.y - (iheight / 2);

/* top will adjust the window position, up or down */

if(top)
pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}
(continued)
290 Peter Norton’s QuickC for Windows

Listing 7-1. (continued)

/ ************************************************************************/
/ * 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


/* *
/
/ a /

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

Dialog Editor - DB.RES, (Untitled)


File Edit Arrange Options Help
(59, 30) cx: 122 Dig. Sym.: ±
cy: 94 (181, 124) Caption: Database

Database

A ab

na OK |

®
_ j.

Cancel
- T

i*i *1
T

□ ■

Figure 7-7. Our Database Dialog Box Template.

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:

LBN_ERRSPACE List box cannot get enough memory space


LBN_SELCH AN GE Selection was changed
LBN_DBLCLK List box selection was double clicked
LBN_SELCANCEL Selection was canceled
LBNSETFOCUS List box got the focus
LBN KILLFOCUS List box lost the focus
Dialog Boxes: Scroll Bars and Listboxes 293

Dialog Editor - DB.RES, (Untitled]


Eu
.
Hie
{6, BJ I
5 i_ist Box Stylesy I,
L-
i— i ' ■ n ■ ■
List dox styles
— ±
cy: 8'
2i j Standard; 1 1 No Integral Height
H Border EH Want Keyboard Input
i 1_I Sort EH Multi-Column
1
[X] Notify EH Multiple Selection
EH Vert. Scroll Bar |_| Extended Selection
A


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
.

[X] Visible n Group


l*l*J 1 1 Disabled S Tab Stop
— 1
□ I | OK | Cancel Help |

|
1__!_
\ L—J |

Figure 7-8. List Box Options.

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):

100 DIALOG 59, 30, 122, 94


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Database"
FONT 8, "Helv"
BEGIN
LISTBOX 101, 6, 6, 49, 83, LBS_SORT ! WS_VSCROLL ! WS_TABSTOP
PUSHBUTTON "OK", IDOK, 70, 20, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 70, 56, 40, 14
END

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.

As we might expect, QuickCase:W has given us these two procedures (among


others): WndProc( ) and DBMsgProc( ). In WndProc( ), we can make the
menu commands active, and in DBMsgProc( ), we can add support for the
dialog box commands. We can use code from our earlier phone book applica¬
tion because that stored the names and numbers neatly in the arrays as-
NameString[ ] and asNumberString[ ].

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:

static char asNameString[10] [20], asNumberString[10] [20] ; <—


static int anNameStrlen[10], anNumberStrlen[10] ; <—
static BOOL bEnteringNumber; <—
static int cxChar, cyChar, nNames; <—

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):

static int anNameStrlen[10], anNumberStrlen[10];


static BOOL bEnteringNumber;
static char asNameString[10][20], asNumberString[10][20];
static int cxChar, cyChar, nNames;
Dialog Boxes: Scroll Bars and Listboxes 295

/********************************************************************** /
/* *
/
/* Main Window Procedure */
/* *
/
y********************************************************************** /

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. */

{
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_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End o f WM._SIZE */
case WM_PAINT: /* code for the window's client area */
/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM CHAR:
Dialog Boxes: Scroll Bars and Listboxes 297

—> 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 */
: asNameString[nNames][anNameStrlen[nNames]] = (char) \
: wParam;
: anNameStrlen[nNames]++;
: TextOut(hDC, 0, 0, (LPSTR) asNameString[nNames], \
: anNameStrlen[nNames]);
: SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
: asNameString[nNames], anNameStrlen[nNames]), 0);
}
: ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
: }
—> break;

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

List Box Message Means

LB_ADDSTRING Add a string


LBJNSERTSTRING Insert a string
LB_DELETESTRIN G Delete a string
LB_RESETCONTENT Clear box
LB_SETSEL Set selection state
LB_SETCURSEL Set currently selected item
LB_GETSEL Get selection state
LB_GETCURSEL Get currently selected item
LB_GETTEXT Get item at some index
LB_GETTEXTLEN Get item’s text length
LBGETCOUNT Get number of items in list box
LB_SELECTSTRIN G Select a string
LB_DIR Display directory files
LB_GETT OPINDEX Get item at top (not 0 if box is scrolled)
LBFINDSTRING Locate a string
LB_GETSELCOUNT Get selection count
LB_GETSELITEMS Get indices of selected items
LB_SETTABSTOPS Set tabs
LB_GETHORIZONTALEXTENT Width in pixels box can be scrolled horiz.
LB_SETHORIZONTALEXTENT Width in pixels box can be scrolled vert.
LB_SETCOLUMNWIDTH Set col. width in multi-column boxes
LB_SETTOPINDEX Set top item’s index
LB_GETITEMRECT Get bounding rectangle for item
LB_GETITEMDATA Get user supplied data associated with
item
LBSETITEMDATA Set user supplied data associated with item
LB_SELITEMRAN GE Set a range of items

Table 7-1. List Box Control Messages.

To send our LB INSERTSTRING messages, we’ll have to place code in the


WM INITDIALOG case of DBMsgProc( ). We can do that like this in the
dialog box procedure, filling the list box (ID number 101 as we designed it in
the dialog editor) with the name of each data item like this (where nNames is
the total number of data items in our database):
Dialog Boxes: Scroll Bars and Listboxes 299

y^*********************************************************************** /
/* */
/* Dialog Window Procedure */
I* / *

/* 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. */
/* */
y************************************************************************ /

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

Figure 7-9. Our Database at Work.

To handle double clicks, we have to intercept WM_COMMAND messages


from control 101 (our list box). The LBN notification code we’re looking for
is LBNDBLCLK, and it will be encoded in HIWORD(lParam). In fact, han¬
dling this case is easy; we want this action to be the same as if the user had
highlighted a selection and then clicked the OK button. This means that we
can simply send ourselves an IDOK message and handle everything in the OK
button case:

/************************************************************************ /
/* / *

/* Dialog Window Procedure */


/* * /

/************************************************************************ /

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:

nLen = (int) SendMessage(hWndListBox, LB_GETTEXT, nlndex, \


(LONG) (LPSTR) szString);

Or, we could have highlighted an item (i.e., selected it) with index nlndex like
this:

SendMessage(hWndListBox, LB_SETCURSEL, nlndex, OL) ;

We can even get the length of the current selection like this:

nLen = SendMessage(hWndListBox, LB_GETTEXTLEN, nlndex, OL) ;

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

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 */

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;

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;
}
304 Peter Norton’s QuickC for Windows

break; /* End of WM_COMMAND */

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.

Listing 7-2. Mydb.c.

/* QuickCaserW KNB Version 1.00 */


#include "MYDB.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages


*/
int nRc; /* return value from Register Classes
*/

st rcpy(s zAppName, "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;
}
}

/* create application's Main window */


Dialog Boxes: Scroll Bars and Listboxes 305

Listing 7-2. (continued)

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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */

CwUnRegisterClasses();
return msg.wParam;
} /* End of WinMain */

static int anNameStrlen[10], anNumberStrlen[10];


static BOOL bEnt e ringNumbe r;
static char asNameString[10][20], asNumberString[10][20];
static int cxChar, cyChar, nNames;

(continued)
306 Peter Norton’s QuickC for Windows

Listing 7-2. (continued)

/ ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ /
/★ *
/
/ * 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

Listing 7-2. (continued)

FARPROC lpfnDBMsgProc;

lpfnDBMsgProc = MakeProcInstance((FARPROC)DBMsgProc, hlnst);


nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
lpfnDBMsgProc);
FreeProcInstance(lpfnDBMsgProc);
}
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:
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_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will'sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
(continued)
308 Peter Norton's QuickC for Windows

Listing 7-2. (continued)

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

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 */
asNameString[nNames][anNameStrlen[nNames]] = (char) wParam;
anNameStrlen [nNames ] ++;
TextOut(hDC, 0, 0, (LPSTR) asNameString[nNames], \
anNameStrlen[nNames]);
SetCaretPos((int)GetTextExtent(hDC, (LPSTR) \
asNameString[nNames], anNameStrlen[nNames]), 0);
}
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
}
break;

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
Dialog Boxes: Scroll Bars and Listboxes 309

Listing 7-2. (continued)

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

Listing 7-2. (continued)

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

Listing 7-2. (continued)

/************************************************************************ /
/ * *
/
/* 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 */

/* /
/************************************************************************ /

void cwCenter(hWnd, top)


HWND hWnd;
int top;

(continued)
312 Peter Norton’s QuickC for Windows

Listing 7-2. (continued)

{
POINT pt;
RECT swp;
RECT rParent;
int iwidth;
int iheight;

/* get the rectangles for the parent and the child */


GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, &rParent);

/* calculate the height and width for MoveWindow */


iwidth = swp.right - swp.left;
iheight = swp.bottom - swp.top;

/* find the center point and convert to screen coordinates */


pt.x = (rParent.right - rParent.left) / 2;
pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point */


pt.x = pt.x - (iwidth / 2);
pt.y = pt.y - (iheight / 2);

/* top will adjust the window position, up or down */


if(top)
pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}

/************************************************************************ /
/* 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

Listing 7-2. (continued)

WNDCLASS wndclass; /* struct to define a window class */


memset(&wndclass, 0x00, sizeof(WNDCLASS));

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):

CBN_ERRSPACE Combo box cannot get enough memory space


CBN_SELCHAN GE Selection was changed
CBNJDBLCLK Combo box selection was double clicked
CBNSETFOCUS Combo box got the focus
CBN KILLFOCUS List box lost the focus

In addition, however, we can also get these notification codes in wParam when
the text in the text box is changed:

CBN_EDITCHANGE Text in text box edited


CBNEDITUPDATE Text in text box updated from list
CBNJDROPDOWN Drop down combo box was opened

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

Combo Box Message Means

CB_GETEDITSEL Get text box selection


CB_LIMITTEXT Limit text length in text box
CB_SETEDITSEL Selects characters in the text box
CB_ADDSTRING Add a string to the list box
CB_DELETESTRIN G Delete a string from the list box
CBDIR Display directory data
CB_GETCOUNT Get count of items in list box
CB_GETCURSEL Get index of selected item if any
CB_GETLBTEXT Get string from list box
CB_GETLBTEXTLEN Get length of string in list box
CB_IN SERTSTRIN G Insert a string
CB_RESETCONTENT Clear list and text boxes
CB_FINDSTRIN G Find a string
CB_SELECTSTRING Select string matching a prefix
CBSETCURSEL Select a string
CBSHOWDROPDOWN Shows or hides dropdown list box
CB_GETITEMDATA Get user supplied data for this item
CB SETITEMDATA Set user supplied data for this item

Table 7-2. Combo Box Control Messages.

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.

Working with files is integral to programming, and the file-handling capabili¬


ties in QuickC are good; in this chapter, we’ll see how to interface them to
Windows. Our first program will be a file scanner, where we read in existing
files and display them in a window. After that, we’ll update our database
program so that it can store and read its data to and from disk. Now let’s get
started immediately with the file scanner project.

An Open File... Dialog Box


In Windows, we can often use the standard C functions when working with
files (with the exception of opening files, as we’ll see). The major difference is
in the user interface, where the user specifies what files they want to read from
or write to. As we know, these kinds of operations are usually accomplished
with dialog boxes. For that reason, our first example will be a file scanner —

315
316 Peter Norton’s QuickC for Windows

Figure 8-1. Our Open... Dialog Box Template.

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).

Displaying Filenames in a List Box

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. */
/* */
/************************************************************************/

BOOL FAR PASCAL OPENMsgProc(HWND hWndDlg, WORD Message, WORD wParam,\


LONG 1Param)
{
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);
318 Peter Norton’s QuickC for Windows

break; /* End of WM_CLOSE */

case WM_COMMAND:
switch(wParam)
{
case 101: /* List box */
break;

case 102: /* Button text: "Open" */


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 */

The case we want to work on first is WMINITDIALOG. In this case, we’ll


determine the vertical font size used in the parent window (so we can correctly
display the contents of the file we’re going to read by skipping to the next line
when we encounter a carriage return) and we’ll set up our list box to display
filenames. Getting the font height is easy; we simply use GetTextMetrics( ) as
before, passing it to the handle of our parent window:

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

is simply (other examples might be “*.dat”, “chapPP.txt”, and so on), so


our call to SendMessage( ) looks like this:

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 */

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:

memset(&ps, 0x00, sizeof(PAINTSTRUCT));

hDC = BeginPaint(hWndDlg, &ps);

—> getcwd (szDriveAndDir 30);

—> TextOut(hDC, 135, 4, (LPSTR) szDriveAndDir, strlen(szDriveAndDir));

EndPaint(hWndDlg, &ps);

break;
320 Peter Nortons QuickC for Windows

Figure 8-2. Our File Open Dialog Box at Work.

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){

—> SendMessage(hWndDlg, WM_COMMAND, 102, 0L) ;


}
break;

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.

nlndex = (int) SendMessage(GetDlgItem(hWndDlg, 101), \


LB_GETCURSEL, 0, 0L) ;
SendMessage(GetDlgItem(hWndDlg, 101), LB_GETTEXT, \
nlndex, (LONG) (LPSTR) szFileName);

Note that the LB GETSEL message caused SendMessage ( ) to return the


index of the current selection, which we stored in nlndex. Next, we passed
that index (in wParam) as well as the address of a text buffer named
szFileName (in lParam) to the list box when we sent a LB GETTEXT message.
At that point, szFileName gets filled with the currently selected file name. Now
we have the name of the file we’re supposed to open. All that remains is to
open the file and to display its contents.

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){

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");

Note that we pass a pointer to a structure of type OFSTRUCT, which we call


“of,” in the call to OpenFile( ). This structure is defined like this (in win-
dows.h):

typedef struct tagOFSTRUCT


{
BYTE cBytes;
BYTE fFixedDisk;
WORD nErrCode;
BYTE reserved[4];
BYTE szPathName[128];
} OFSTRUCT;

In addition, we pass an OF READ flag to OpenFile ( ), indicating that we want


to open the file only for reading. All the OpenFile ( ) flags appear in Table 8-1
— note the wide range of possibilities here, including the possibility of shar¬
ing files with other applications (the prefix OF stands for OpenFile( )).

The OF_REOPEN OpenFile ( ) flag is very useful; in a multi-tasking envi¬


ronment like Windows, it’s best to keep files open only when you have
execution control. If you reopen a file with OF REOPEN, Windows uses
the information already in the OFSTRUCT structure (i.e., you pass a
pointer to an OFSTRUCT structure in all OpenFile ( ) calls) to open the file
quickly. Note that you can also delete files with the OF_DELETE flag, and,
using the OF_PROMPT flag, your program can actually ask the user to
insert a diskette with the correct file if that file was not found.
Using Files in QuickC for Windows 323

Flag Means

OF_READ Open file for reading


OFWRITE Open file for writing
OF_READ WRITE Open file for both reading and writing
OF_SHARE_COMPAT Open file and allow others complete access
OFSHAREEXCLU SIVE Open file and deny others all access
OF_SHARE_DENY_WRITE Open file and deny others write access
OF_SHARE_DENY_READ Open file and deny others read access
OF_SHARE_DENY_NONE Open file but do not deny access to others
OFPARSE Fill OFSTRUCT struct, but nothing else
OF_DELETE Delete the file
OF_VERIFY Open, verify file’s date/time are unchanged
OF_CANCEL Add a Cancel button to the OF prompt box
OF_CREATE Make new file or truncate existing one to 0
OFPROMPT If file isn’t found, ask for new diskette
OFEXIST Open and close file (i.e. test if exists)
OF_REOPEN Reopen file using stored file information

Table 8-1. OpenFile( ) Flags.

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

“r” Open (existing) file for reading.


a m
w Open (create if necessary) file for writing
“a” Open (create if necessary) file for appending
“r+” Open (existing) file for reading and writing
u . ??
w+ Create and open file for reading and writing
a

“a+” Open (create if necessary) file for reading and appending


“rb” Open a binary file for reading
“wb” Open a binary file for writing
“ab” Open a binary file for appending
“rt” Open a text file for reading
44 , ??
Create a text file for writing
Wt
“at” Open a text file for appending
“r+b” Open a binary file for reading and writing
“w+b” Create a binary file for writing
“a+b” Open a binary file for appending
“r+t” Open a text file for reading and writing
“w+t” Create a text file for reading and writing
“a+t” Open a text file for reading and writing

Table 8-2. File Opening Options.

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

100 DIALOG 68, 26, 115, 114


STYLE DSMODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAI
CAPTION ■■Calculator*'
FONT 8, "Helv"
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,
LTEXT "0", 105, 41, 90, 27, 11
END

Figure 8-3. Our File Scanner Program at Work.

Listing 8-1. Fopen.c.


/* QuickCaserW KNB Version 1.00 */
#include "FOPEN.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display
/ ***********************************************************************

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

st rcpy(s zAppName, "FOPEN") ;


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;
}
}
Using Files in QuickC for Windows 327

Listing 8-1. (continued)

/* create application's Main window


hWndMain = CreateWindow(
s zAppName, /* Window class name */

"File Scanner", /* Window's title */

WS_CAPTION ! /* Title and Min/Max */


WS_SY SMENU I /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */

WS_MAXIMIZEBOX j /* Add maximize box */

W S_THICKFRAME j /* 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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* 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 */

(continued)
328 Peter Norton’s QuickC for Windows

Listing 8-1. (continued)

/* 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 */

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

Listing 8-1. (continued)

break; /* End of WM_COMMAND */

case WM_CREATE:
break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 - */


/* */

(continued)
330 Peter Nortons QuickC for Windows

Listing 8-1. (continued)

/* 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 OPENMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG 1Param)
{
static char szDriveAndDir[30], szFileName[30];
static int cyChar;
char cBuff[CBUFFSIZE], cChar;
int nlndex, nxlndex, nylndex, MSDOSHnd;
PAINTSTRUCT ps;
HWND hWnd;
HDC hDC;
OFSTRUCT of;
FILE * fFile;
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) "*.*");
/* 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

Listing 8-1. (continued)

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;

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(1feof(fFile) && (nxlndex < CBUFFSIZE)){
cChar = (char) fgetc(fFile);
if (cChar == '\r'){
fgetc(fFile);
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;

(continued)
332 Peter Norton’s QuickC for Windows

Listing 8-1. (continued)

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));

/* load WNDCLASS with window's characteristics */


wndclass.style = CS_HREDRAW I 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 = Loadlcon(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);
UsingFiles in QuickC for Windows 333

Listing 8-1. (continued)

} /* End of nCwRegisterClasses */

/************************************************************************/

/* cwCenter Function */
/* */
/* centers a window based on the client area of its parent */
/* */
/************************************************************************/

void cwCenter(hWnd, top


HWND hWnd;
int top;
{
POINT pt;
RECT swp;
RECT rParent;
int iwidth;
int iheight;

/* get the rectangles for the parent and the child */


GetWindowRect(hWnd, &swp);
GetClientRect(hWndMain, &rParent);

/* calculate the height and width for MoveWindow */


iwidth = swp.right - swp.left;
iheight = swp.bottom - swp.top;

/* find the center point and convert to screen coordinates */


pt.x = (rParent.right - rParent.left) / 2;
pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point */


pt.x = pt.x - (iwidth / 2);
pt.y = pt.y - (iheight / 2);

/* top will adjust the window position, up or down */


if(top)
pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);

}
(continued)
334 Peter Norton's QuickC for Windows

Listing 8-1. (continued)

/************************************************************************ /
/* 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 */

Listing 8-2. Fopen.h.

/* QuickCaserW KNB Version 1.00 */


iinclude <windows.h>
((include <string.h>
((include <direct.h>
((include <errno.h>
((include <stdio.h>
#define IDM_FILE 1000
#define IDM_F_OPEN 1050
#define IDM_F_EXIT 1150
((define CBUFFSIZE 80

((define IDS_ERR_REGISTER_CLASS 1
((define IDS_ERR_CREATE_WINDOW 2

char szString[128]; /* variable to load resource strings */

char szAppName[20]; /* class name for the window */


HWND hlnst;
HWND hWndMain;

void cwCenter(HWND, int);

LONG FAR PASCAL WndProc(HWND, WORD, WORD, LONG);


BOOL FAR PASCAL OPENMsgProc(HWND, WORD, WORD, LONG);
int nCwRegisterClasses(void);
void CwUnRegisterClasses(void);
Using Files in QuickC for Windows 335

A Save As... Dialog Box


Now that we’ve opened and read from files, the next step is to create and write
to them. For example, we can modify our database program from the last
chapter into a more general database program that can save and retrieve files
to and from disk. Doing this will also give us exposure to another technique in
QuickC for Windows — how to use multiple dialog boxes. That is, we already
have one dialog box in our database program, and it’s defined like this in the
file db.dlg (produced by the dialog editor):

100 DIALOG 59, 30, 122, 94


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Database"
FONT 8, "Helv"
BEGIN
LISTBOX 101, 6, 6, 49, 83, LBS_SORT ! WS_VSCROLL ! WS_TABSTOP
PUSHBUTTON "OK", IDOK, 70, 20, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 70, 56, 40, 14
END

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:

100 DIALOG 63, 30, 132, 97


STYLE DS_MODALFRAME i WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Open File..."
FONT 8, "Helv"
BEGIN
LISTBOX 101, 4, 3, 67, 94, LBS_SORT ! WS_VSCROLL ! WS_TABSTOP
PUSHBUTTON "Open", 102, 84, 20, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 84, 54, 40, 14
END

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

#include "DB.DLG" <—

#include "OPEN.DLG" <—

#include "SAVE.DLG" <—

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):

100 DIALOG 59, 30, 122, 94 <—


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Database"
FONT 8, "Helv"
BEGIN
LISTBOX 101, 6, 6, 49, 83, LBS_SORT ! WS_VSCROLL j WS_TABSTOP
PUSHBUTTON "OK", IDOK, 70, 20, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 70, 56, 40, 14
END

In addition, the Open File... dialog box is defined as resource 100:

100 DIALOG 63, 30, 132, 97 <—


STYLE DS_MODALFRAME I WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
UsingFiles in QuickC for Windows 337

CAPTION "Open File..."


FONT 8, "Helv"
BEGIN
LISTBOX 101, 4, 3, 67, 94, LBS_SORT I WS_VSCROLL ! WS_TABSTOP
PUSHBUTTON "Open", 102, 84, 20, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 84, 54, 40, 14
END

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);

—> nRc = DialogBox(hlnst, MAKEINTRESOURCE(100) , hWnd, lpfnDBMsgProc);


FreeProcInstance(lpfnDBMsgProc);
}
break;

One solution is to edit the file open.dig ourselves and make this dialog box,
say, resource number 200 instead:

200 DIALOG 63, 30, 132, 97 <—


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE ! WS_CAPTION ! WS_SYSMENU
CAPTION "Open File..."
FONT 8, "Helv"
BEGIN
LISTBOX 101, 4, 3, 67, 94, LBS_SORT ! WS_VSCROLL ! WS_TABSTOP
PUSHBUTTON "Open", 102, 84, 20, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 84, 54, 40, 14
END

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

Dialog Editor - SAVE.RES, (Untitled]


File Edit Arrange Options Help
Symbol: *1
T ext:

Figure 8-4. Save As... Dialog Box Template.

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

Figure 8-5. Creating Multiple Dialog Boxes

If we examine save.dig, this is what we find (note that this is now resource
300):

300 DIALOG 57, 33, 125, 99


STYLE DS_MODALFRAME ! WS_POPUP ! WS_VISIBLE j WS_CAPTION ! WS_SYSMENU
CAPTION "Save As ..."
FONT 8, "Helv"
BEGIN
COMBOBOX 101, 3, 10, 63, 85, CBS_SIMPLE ! CBS_SORT !
WS_VSCROLL ! WS_TABSTOP
PUSHBUTTON "Save", 102, 77, 28, 40, 14
PUSHBUTTON "Cancel", IDCANCEL, 77, 64, 40, 14
END

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
«»

Figure 8-6. Our Database Program’s New File Menu.

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:

/* Dialog Window Procedure */


/* */
/************************************************************************ j

BOOL FAR PASCAL SAVEMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG lParam)
{

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 101: /* Combo Box */


break;

case 102: /* Button text: "Save" */


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 SAVEMsgProc */

Some parts of SaveMsgProc( ) will be very similar to OPENMsgProc( ). For


example, we can fill the combo box in our new Save As... dialog box with file
names by sending a CB_DIR message instead of LB_DIR (see the end of the
previous chapter for more information about what messages work with combo
boxes). In this example, let’s give our database files the extension .dat like
this:

BOOL FAR PASCAL SAVEMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG lParam)

static int cyChar;


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);
342 Peter Norton’s QuickC for Windows

—> SendMessage(GetDlgltem(hWndDlg, 101), CB_DIR, 0, (LONG) \


(LPSTR) "*.DAT");
/* initialize working variables */
break; /* End of WM_INITDIALOG */

In addition, we can print the current working directory in our dialog box as
before, with getcwd( ) and TextOut( ) in the WM_PAINT case:

BOOL FAR PASCAL SAVEMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG 1Param)
{
static char szDriveAndDir[30]
static int cyChar;
PAINTSTRUCT ps;
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(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);

—> getcwd(szDriveAndDir, 30);

—> TextOut(hDC, 120, 4, (LPSTR) szDriveAndDir, strlen(szDriveAndDir));

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

Figure 8-7. Our Save As... Dialog Box.

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:

BOOL FAR PASCAL SAVEMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG 1Param)

{
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){

—> case 101: /* Combo Box */

—> if(HIWORD(1Param) == CBN_DBLCLK)

—> SendMessage(hWndDlg, WM_COMMAND, 102, 0L);


break;

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.

—> GetDlgltemText(hWndDlg, 101, (LPSTR) szFileName, 30);

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);

—> MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_CREATE);

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

—> if(MSDOSHnd == -1){ //error

—> MessageBox(hWndDlg, (LPSTR) "File Error", \

—» (LPSTR) "Error", MB_ICONEXCLAMATION ! MB_OK) ;

—> break;

In actual applications, all aspects of file handling should be checked for


errors like this. It is worth noting that file handling probably generates the
most nonbug run-time errors in Windows applications.

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;
}

—> EndDialog(hWndDlg, TRUE);

—> 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[ ] [ ]

asNameString [0] “Apples’ asNumberString[0]

‘Oranges”

“Bananas”

anNumberStrlen [

anNameString[0] anNumberStrlen [0]

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:

NameString[ ] = “Apples” mydata [0]


NameStrlen = 6
NumberString [ ] = “214”
NumberStden = 3

NameString[ ] = “Oranges” mydata[l]


NameStrlen = 7
NumberString [ ] = “119”
NumberStrlen = 3

NameString[ ] = “Bananas” mydata [2]


NameStrlen = 7
NumberString [ ] = “327”
NumberStrlen = 3

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");

—> for(nIndex = 0; nlndex < nNames; nIndex++){

—> fwrite(&mydata[nlndex], sizeof(struct myentry), 1, fFile);

-> }

—> 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" */
»

// 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");
for(nIndex = 0; nlndex < nNames; nlndex++){
fwrite(&mydata[nlndex], sizeof(struct myentry), 1, fFile);
}
fclose(fFile);

—> hWnd = GetParent(hWndDlg) ;


: 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);
: EndDialog(hWndDlg, TRUE);

—> 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) ) {

—> fread(&mydata[nNames], sizeof(struct myentry), 1, fFile);


nNames++;
}

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

Figure 8-8. Our Database Program at Work.

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.

Listing 8-3. Mydb.c.

/* QuickCaserW KNB Version 1.00 */


#include "MYDB.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, .int nCmdShow)

{
(continued)
352 Peter Nortons QuickC for Windows

Listing 8-3. (continued)

/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

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;

}
}

/* create application's Main window */


hWndMain = CreateWindow(
szAppName, /* Window class name */
"Database Example", /* Window's title */
WS_CAPTION I /* Title and Min/Max */
WS_SYSMENU I /* Add system menu box */
WS_MINIMIZEBOX ! /* Add minimize box */
WS_MAXIMIZEBOX ! /* Add maximize box */
WS_THICKFRAME 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)

{
UsingFiles in QuickC for Windows 353

Listing 8-3. (continued)

Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof (szString));


MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* End of WinMain */

static BOOL bEnteringNumber;


static int cxChar, cyChar, nNames;
struct myentry{
char Namestring[20];
char Numberstring[20] ;
int NameStrlen;
int Numberstrlen;
} mydata[10];

/************************************************************************/
/* */
/* 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 */

HBITMAP hBitmap=0; //* handle for bitmaps */

HDC hDC; /* handle for the display device */


(continued)
354 Peter Norton’s QuickC for Windows

Listing 8-3. (continued)

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_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

Listing 8-3. (continued)

{
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;

lpfnDBMsgProc = MakeProcInstance((FARPROC)DBMsgProc, hlnst);


nRc = DialogBox(hlnst, MAKEINTRESOURCE(100), hWnd, \
lpfnDBMsgProc);
FreeProcInstance(lpfnDBMsgProc);
}
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);
GetTextMetries(hDC, &tm);

(continued)
356 Peter Norton’s QuickC for Windows

Listing 8-3. (continued)

cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
CreateCaret(hWnd, NULL, cxChar/4, cyChar);
SetCaretPos(0, 0);
ShowCaret(hWnd);
ReleaseDC(hWnd, hDC);
break;

&

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

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 */


rrydata[nNames].Numberstring[mydata[nNames].NumberStrlen] \
= (char) wParam;
mydata[nNames].NumberStrlen++;
TextOut(hDC, 0, 2 * cyChar, (LPSTR)
mydata[nNames].Numberstring, \
mydata[nNames].NumberStrlen);
Using Files in QuickC for Windows 357

Listing 8-3. (continued)

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;

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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. */
/* */
/************************************************************************/

(continued)
358 Peter Norton’s QuickC for Windows

Listing 8-3. (continued)

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

Listing 8-3. (continued)

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 */

/************************************************************************ /
/* / *

/* 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 OPENMsgProc(HWND hWndDlg, WORD Message, WORD wParam, \


LONG 1Param)

static char szDriveAndDir[30], szFileName[30];


static int cyChar;
char cBuff[80], cChar;
int nlndex, MSDOSHnd;
PAINTSTRUCT ps;
HWND hWnd;
HDC hDC;
OFSTRUCT of;
FILE *fFile;

(continued)
360 Peter Norton’s QuickC for Windows

Listing 8-3. (continued)

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 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);
Using Files in QuickC for Windows 361

Listing 8-3. (continued)

fFile = fdopen(MSDOSHnd, "r");


nlndex = 0;
nNames = 0 ;
while(!feof(fFile) ) {
f read (Smydata [nNames] , sizeof (struct myentry) , 1, fFile);
nNames++;
}
nNames--;
fclose(fFile);
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[0].Namestring, \
mydata[0].NameStrlen);
TextOut(hDC, 0, 2 * cyChar, (LPSTR) mydata[0].Numberstring,\
mydata[0].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 OPENMsgProc

/* */

/* Dialog Window Procedure */

*/

(continued)
362 Peter Norton’s QuickC for Windows

Listing 8-3. (continued)

/* 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 SAVEMsgProc(HWND hWndDlg, WORD Message,^ WORD wParam, \


LONG 1Param)
{

static char szDriveAndDir[30], szFileName[30];


static int cyChar;
char cBuff[80], cChar;
int nlndex, nxlndex, nyIndex, MSDOSHnd;
PAINTSTRUCT ps;
HWND hWnd;
HDC hDC;
OFSTRUCT of;
FILE *fFile;
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;
Using Files in QuickC for Windows 363

Listing 8-3. (continued)

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: /* Combo Box */


if(HIWORD(lParam) = = CBN_DBLCLK)
SendMessage(hWndDlg, WM_COMMAND, 102, OL) ;
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 I MB_OK);
break;
}
fFile = fdopen(MSDOSHnd, "w");
for(nIndex = 0; nlndex < nNames; nlndex++){
fwrite(&mydata[nlndex], sizeof(struct myentry), 1, fFile);
}
fclose(fFile);
hWnd = GetParent(hWndDlg);
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);
EndDialog(hWndDlg, TRUE);
break;

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

Listing 8-3. (continued)

}
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));

/* 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 */

/************************************************************************i
/* cwCenter Function */
Using Files in QuickC for Windows 365

Listing 8-3. (continued)

/ * *
/
/* centers a window based on the client area of its parent * /
/* -k
/
/ ************************************************************************ /

void cwCenter(hWnd, top)

HWND hWnd;

int top;

{
POINT pt;

RECT swp ;

RECT rParent;

int iwidth;

int iheight;

/* get the rectangles f */

GetWindowRect(hWnd, &swp);

GetClientRect(hWndMain, &rParent);

/* calculate the height and width for MoveWindow */

iwidth = swp.right - swp.left;

iheight = swp.bottom - swp.top;

/* find the center point and convert to screen coordinates */

pt.x = (rParent.right - rParent.left) / 2;


pt.y = (rParent.bottom - rParent.top) / 2;
ClientToScreen(hWndMain, &pt);

/* calculate the new x, y starting point */

pt.x = pt.x - (iwidth / 2);

pt.y = pt.y - (iheight / 2);

/* top will adjust the window position, up or down */

if(top)

pt.y = pt.y + top;

/* move the window */


MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);

/★★★★★★★★★★■a-************************************************************* /
/* CwUnRegisterClasses Function * /
/* /
/* Deletes any refrences to windows resources created for this * /

(continued)
366 Peter Nortons QuickC for Windoivs

Listing 8-3. (continued)

/* 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 */

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.

From Arrays to Pointers


By now we’re quite familiar with the use of arrays in Windows programs. If we
want to use an array, we first have to declare it, like this:

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, \


LONG 1Param)
{
—> int array[10];

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:

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, \


LONG lParam)
{
int array[10], i;

—> array[i] = 7;

However, as we know, the name of an array in C is really a pointer; that is to


say, array[0] is equal to *array— the two expressions are interchangeable. In
fact, we can say this for the element array [i]:

array [i] = * (array + i)

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:

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, \


LONG lParam)
{
int array[10], i;

—> *(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.

How to Make Your Windows Programs Faster


For example, let’s take a look at this program:

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, \


LONG lParam)
{
char arrayl[] = "Hello, world.\n", mychar;
int i;
Fast Data Handling 369

case WM_PAINT: /* code for the window's client area */


for(i = 0; i < 14; i++)
mychar = array[i];
}

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:

LONG FAR PASCAL WndProc(HWND hWnd, WORD Message, WORD wParam, \


LONG 1Param)
{
char array[] = "Hello, world.\n", *array_pointer;
char mychar;
int i ;

case WM_PAINT: /* code for the window's client area */

—> array_pointer = array;


—> for(i = 0; i < 14; i + +)
—> mychar = *array_pointer++;
}

Now we’ve added a pointer to the beginning of the array, named


array_pointer. Each time through the loop, we increment array pointer and
assign the character *array__pointer to mychar. The result is the same as before
— the string “Hello, world.Xn” is assigned to mychar, one character at a time.
However, even though we did the same thing (incremented through the
elements of an array), no multiplications were involved. In other words, we’re
moving through the array simply by incrementing a pointer. We start like this:
370 Peter Norton’s QuickC for Windows

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

When the compiler sees an expression like array_pointer++, it simply encodes


that as array_pointer + sizeof(char). Similarly, if we were pointing to an array
of doubles, array_pointer++ would simply become array_pointer + sizeof (dou¬
ble) . This is an improvement over arrays: We didn’t have to find an element’s
address from scratch. Since hardware multiplications are very slow instruc¬
tions, we’ve saved a good deal of time. In other words, incrementing (or
decrementing) a pointer through the elements of an array is far faster than
generating a complete address for the item array [i], and, using pointer nota¬
tion, we can take advantage of that fact.

How to Save Some Memory


Besides frequently being faster, there are other reasons to consider pointer
notation instead of array notation. Consider an array of character strings:

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 [0] [ ]-► H e 1 1 o 0

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

Here we have a number of pointers, each pointing to character strings. No


space is wasted — the strings can be stored one right after the other. Fre¬
quently, memory is at a premium, and using pointers instead of arrays can save
much room. (In addition, we can also support variable-length fields this way.)

Fast Sorting Techniques


Professional database programs are responsible for handling a great deal of data
as stored in records, and one of the most important operations they perform is
sorting those records. However, these programs don’t actually move records
themselves when sorting them, but only pointers to the records; this method is a
great deal faster. For example, imagine that the situation was as shown below,
where we have three elements in the array my_data[ ], each of which is a struc¬
ture, and a pointer to each of those structures in the array my_pointers[ ]:

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]

pointer A ► 3 -*- key

-*- big_array[ ]

my_pointers[l] my_data [ 1 ]

pointer B 2 +- key

•*- big_array[ ]

my_pointers[2] my_data[2]

pointer C 1 -*- key

big_array[ ]

Now the pointer in my_pointers[0] points to my_data[2], and so on. That


means that my_pointer[0]->key has changed from 3 to 1, because that pointer
is now pointing at the last record:

my_pointers[0]->key: 1
my_pointers[l]->key: 2
my_pointers[2]->key: 3

Of course, we can reach big_array[ ] using my pointersf ] also, so it would


look to our program as if all the records have been moved around. In other
words, we have changed the apparent ordering of the records, just by moving
the pointers around used to reference them. This is a very quick way to sort
Fast Data Handling 373

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.

How Shell Sorts Work


The standard shell sort is alway popular among C programmers. It works like
this — say you had a one-dimensional array with these values in it:

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

In this case, 8 is greater than 4, so we switch the elements, and go on to


compare the next pair:
i l
4 7 6 5 8 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.

Again, 7 is greater than 3, so we switch and go on:


I i
4 3 6 5 8 7 2 1
I_I I-1
We also switch 6 and 2 and then look at the last pair:
i I
4 3 2 5 8 7 6 1
l_I I-1
After we switch them too, we get this as the new list:

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

We switch both pairs and go on, comparing 3 with 1 and 7 with 5:


I I I I
2 3 4 1 6 7 8 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

Now we’d compare as before, switching as necessary, until we try to compare a


value in the first partition to a value in the second partition that isn’t there:
l i
432159876x
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 */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);

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;
}

Here, we’re using a typical technique used by many C programmers in Win¬


dows; since TextOut( ) can only take one single text string, we use the
sprintf( ) function to produce a string that we might print in a DOS C pro¬
gram. In other words, the output here goes to szOutString, not to the screen:

sprintf(szOutString, "my_pointers[%d]~>key: %d", \


i, my_pointers[i]->key);

Next, we can use TextOut( ) to display szOutString:

sprintf(szOutString, "my_pointers[%d]->key: %d", \


i, my_pointers[i]->key);
—> TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));

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):

partition—size = number_items = 10;

do {
partition—size = (partition_size +1) / 2;

}while(partition—size > 1);

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

not divide smoothly into partition_size, leaving us with an additional short


partition on the end. Therefore, we set up number_partitions like this:

partition_size = number_iterns = 10;

do {
partition_size = (partition_size +1) / 2;
—> number_partitions = number_items / partition_size;
—> if (number__items%partition_size) number_partitions++;

}while(partition_size > 1);

Now that we have number_partitions different in partitions, we have to loop


over them, comparing the elements of the current partition to the elements of
the next. That means that we’ll need an inner loop like this:

partition_size = number_items = 10;

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:

partition_size = number_items = 10;

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

The element we want to compare it to is the corresponding element in the


next partition, at location j + partition_size:

j j + partition_size

I I
10 9876 54321
partition_size = 5 I_I I_I

If we were using the array of my_data[ ], we’d compare my_data[j] .key to


my_data[j+partition_size] .key. However, we’re using pointers, so we’ll com¬
pare my_pointers[j]->key to my_pointers[j+partition_size]->key like this (in¬
cluding exchanging the pointers if we need to):

partition_size = number_items = 10;

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

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[japartition_size] = my_pointers[j];
—> my pointers[j] = temp;
}
}
first_index + = partition_size;
}
}while(partition_size > 1);

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];

struct large_data_struct *temp, *my_pointers[10];


int number_items, number_partitions, partition_size;
int first_index, last_index, i, j, cXloc, cYloc;

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

break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);

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;
}

TextOut(hDC,0, cYloc, (LPSTR)"Sorting...", strlen("Sorting..."));

cYloc + = cyChar;

partition_size = number_items = 10;

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;
}

/* Inform Windows painting is complete * */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */

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...

Figure 9-1. Our Shell Sort Example.


Fast Data Handling 383

Listing 9-1. Example of Sorting with Pointers.


/* QuickCase:W KNB Version 1.00 */
#include "SORT.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/***********************************************************************
/* HANDLE hlnstance; handle for this instance */
/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */
long nWndunits ; /* window units for size and location */
int nWndx; /* the x axis multiplier */
int nWndy; /* the y axis multiplier */
int nX; /* the resulting starting point (x, y) */
int nY;
int nWidth; /* the resulting width and height for this */
int nHeight; /* window */

strcpy(szAppName, "SORT");

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 a device independant size and location */

nWndunits = GetDialogBaseUnits();

nWndx = LOWORD(nWndunits);

nWndy = HIWORD(nWndunits);

nX = ( (0 * nWndx) / 4) ;
nY = ((1 * nWndy) / 8);

nWidth = ((200 * nWndx) / 4);

(continued)
384 Peter Norton’s QuickC for Windows

Listing 9-1. (continued)

nHeight = ((114 * nWndy) / 8);

/* create application's Main window */


hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Sort", /* Window's title */
W S_CA PTION /* Title and Min/Max */
WS_SYSMENU /* Add system menu box */
WS_MINIMIZEBOX /* Add minimize box */
WS_MAXIMIZEBOX /* Add maximize box */
WS_THICKFRAME /* thick sizeable frame */
WS_CLIPCHILDREN /* don't draw in child windows */
W S_OVERLA P P ED,
nX, nY, /* X, Y */
nWidth, nHeight, /* Width, Height of window */
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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* 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 */
Fast Data Handling 385

Listing 9-1. (continued)

/* 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];

struct large_data_struct *temp, *my_pointers[10];


int number_items, number_partitions, partition_size;
int first_index, last_index, i, j, cXloc, cYloc;

switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm) ;
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break;

break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /*" End of WM_SIZE */

(continued)
386 Peter Norton’s QuickC for Windows

Listing 9-1. (continued)

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
a

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;
}

TextOut(hDC,0, cYloc, (LPSTR)"Sorting, strlen("Sorting..."));


cYloc += cyChar;

partition_size = number_iterns = 10;

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)
last_index = number_items - partition_size;
for(j = first_index; j < last_index; j++){
if (iry_pointers [ j ]->key > my_pointers [ j+partition_size]->key) {
temp = myjpointers[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);

cYloc = 0;
cXloc = 30 * cxChar;
for(i = 0; i < 10; i++){
Fast Data Handling 387

Listing 9-1. (continued)

sprintf(szOutString, "my_pointers[%d]->key: %d", i,\


rny_Pointers [i] ->key) ;
TextOut(hDC, cXloc, cYloc, (LPSTR) szOutString, \
strlen(szOutString));
cYloc += cyChar;
}

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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));

/* load WNDCLASS with window's characteristics */


wndclass.style = CS_HREDRAW ! CS_VREDRAW ! CS_BYTEALIGNWINDOW;

(continued)
388 Peter Nortons QuickC for Windows

Listing 9-1. (continued)

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).

Schematically, a linked list looks like this:

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:

itemO iteml item2

This is how it might look in code:

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

static int cyChar;

struct mylist{ <—


char msg[20]; <—
struct mylist *ptr; <—
};
int cYloc;
char szOutString[10];
struct mylist *pointer_to_next_item;

struct mylist item2 = { "World.", NULL }; <-

struct mylist iteml = { "there,", &item2 }; <—

struct mylist itemO = { "Hello", &iteml };

switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

—> 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;

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */

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.

Figure 9-2. Linked List Example.


392 Peter Norton’s QuickC for Windows

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).

Searching Your Data


Now that we’ve ordered our data, it becomes much easier to search through.
If our data is unordered, we’d have no choice but to simply check one value
after another until we found a match to our search value, as in this example
program, where we fill an array with the values 10 down to 1 and then search
for the value 5:

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps) ;

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

sprintf(szOutString, "%s", itemO.msg);


TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen(szOutString));

—> //first, fill nArray[]

—> f or(loop_index = 0; loop_index < 10; loop_index++)


nArray[loop_index] = 10 - loop_index;

—> // now search for the value 5

-» f or(loop_index = 0; loop_index < 10; loop_index++)


if(nArray[loop_index] == 5){
—> sprintf(szOutString, "Found in element %d", loop_index);
—> TextOut(hDC, 0, 0, (LPSTR) szOutString, \
strlen(szOutString));
}
}
break;

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

default:
394 Peter Norton’s QuickC for Windows

/* 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 */

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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
Fast Data Handling 395

// Ordered Search Example

—> for(loop_index = 0; loop_index < 10; loop_index++)


—> nArray[loop_index] = loop_index;

—> 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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
// Ordered Search Example
for(loop__index = 0; loop_index < 10; loop_index++)
nArray[loop_index] = loop_index;
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;

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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
// Ordered Search Example
for(loop_index = 0; loop_index < 10; loop_index++)
nArray[loop_index] = loop_index;
SearchValue = 8;
396 Peter Norton’s QuickC for Windows

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;

[Search this partition]

—> }while(Partition > 0);

Let’s first check to see if we’ve found our value — and if so, we can quit:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
// Ordered Search Example
for(loop_index = 0; loop_index < 10; loop_index++)
nArray[loop_index] = loop_index;
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[Testlndex] ==? SearchValue){
—> sprintf(szOutString, "Value of %d in element %d", \
—> SearchValue, Testlndex);
—> TextOut(hDC, 0, cYloc, (LPSTR) szOutString, \
—> strlen(szOutString));
—> break;
}

}while(Partition > 0);


Fast Data Handling 397

If we haven’t found our value, we have to go on to the next iteration of the


loop, setting Testlndex to the middle of either the higher or lower partition,
and then dividing that partition into two new partitions. If the search value is
bigger than the value at our current location in the array, we want to move to
the partition at higher values:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps) ;
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
// Ordered Search Example
for(loop_index = 0; loop_index < 10; loop_index++)
nArray[loop_index] = loop_index;
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[Testlndex] == 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;

}while(Partition > 0);

But if the search value is smaller, on the other hand, we want to move to the
lower partition (which holds lower values):

case WM_PAINT: /* code for the window's client area */


/* obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
398 Peter Norton’s QuickC for Windows

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);
// Ordered Search Example
for(loop_index = 0; loop_index < 10; loop_index++)
nArray[loop_index] = loop_index;
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[Testlndex] == 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);

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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
// Ordered Search Example
for(loop_index = 0; loop_index < 10; loop_index++)
nArray[loop_index] = loop_index;
Fast Data Handling 399

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[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));

—> }
/* 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

Figure 9-3. An Ordered Search Example.


400 Peter Norton’s QuickC for Windows

Listing 9-2. Search.c.


/* QuickCase:W KNB Version 1.00 */
#include "SEARCH.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
J '**★****★*★★★★★★**■*•★**★★ ★ ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

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;
}
}

/* create application's Main window */


hWndMain = CreateWindow(
s zAppName, /* Window class name */
"Search", /* Window's title */
WS_CAPTION 1
/* Title and Min/Max */
WS_SYSMENU 1
1 /* Add system menu box */
WS_MINIMIZEBOX 1
1 /* Add minimize box */
WS_MAXIMIZEBOX 11
/* Add maximize box */
W S_THIC KFRAME 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 */
Fast Data Handling 401

Listing 9-2. (continued)

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;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses();
return msg.wParam;
} /* 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)
{
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;
int nRc=0; /* return code */

int cYloc, Partition, SearchValue, Testlndex, loop_index, nArray[10];


static int cyChar;
(continued)
402 Peter Norton’s QuickC for Windows

Listing 9-2. (continued)

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

// Ordered Search Example

for(loop_index = 0; loop_index < 10; loop_index++)


nArray[loop_index] = loop_index;

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[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

Listing 9-2. (continued)

}
if(nArray[TestIndex] < SearchValue)
Testlndex = Testlndex + Partition;
else
Testlndex = Testlndex - Partition;
}while(Partition > 0);

// Can only find straddled numbers, so add these tests:

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));
}

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */

/* */

(continued)
404 Peter Norton’s QuickC for Windows

Listing 9-2. (continued)

/* 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

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 ! CS_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 */

/************************************************************************/

/* 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

That finishes our chapter on fast data handling. As we mentioned at the


beginning of this chapter, Windows programs can often appear very sluggish
because of the immense amount of I/O going on. One way to help improve
performance is by making your code more efficient — and one way of doing
that is by using some of the techniques we’ve seen in this chapter. Now, in our
final chapter, we’ll look into the process of debugging our programs, which is
a necessary step in the development of almost any Windows application.
\ ' *0

... - .


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.

Fortunately, we have some excellent debugging tools in QuickC for Windows.


The first one we’ll cover is the assertion statement. Assertions are actually
macros; they test statements you pass to them and, if the statement turns out
to be false, they halt the program and inform you that the asertion failed with
a message box. And, after we cover assertions, we’ll move on to the power of
the full screen debugger available in QuickC for Windows.

Using Assertions in Your Programs


Assertions can be very valuable. They can check whether certain conditions
that you thought were true at some point in a program (e.g., positive_number
> 0) are in fact true. Let’s see an example with the following buggy program.
The following WM PAINT case is from a program intended to read in a file
named file.dat, which contains the following text:

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

MSDOSHnd = OpenFile((LPSTR) "files.dat", &of, OF_READ);


fptr = fdopen(MSDOSHnd, "r");

—> hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


—> pBuffer = LocalLock(hLocal); //Get a pointer
Debugging QuickC for Windows Programs 409

fread(pBuffer, 1, 1, fptr);

TextOut(hDC, 0, 0, (LPSTR) pBuffer, 1);

LocalUnlock(hLocal); //Release memory

—> LocalFree(hLocal);
fclose(fptr);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
410 Peter Norton’s QuickC for Windows

/* BeginPaint will sends WM_ERASEBKGND if appropriate */


memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

MSDOSHnd = OpenFile((LPSTR) "files.dat", &of, OF_READ);


fptr = fdopen(MSDOSHnd, "r");
—> assert(fptr != NULL);

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


pBuffer = LocalLock(hLocal); //Get a pointer
—> assert(pBuffer != NULL);
fread(pBuffer, 1, 1, fptr);

TextOut(hDC, 0, 0, (LPSTR) pBuffer, 1);

LocalUnlock(hLocal); //Release memory


LocalFree(hLocal);
fclose(fptr);

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

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

© Expression fptr != NULL File


C:\QCWIN\BIN\ASSERT.C, Line 114

ioniJff
iwiiiniiwiiMnii.

Figure 10-1. Assertion Messagebox.


Debugging QuickC for Windows Programs 411

Assertion Example
T

Figure 10-2. Working Assertion Program.

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

Figure 10-3. QuickC for Window’s Debug Menu.

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 */

case WM_MOVE: /* code for moving the window */


v break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */
Debugging QuickC for Windows Programs 413

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

//Following code has bugs in it.

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
fclose(fptr);

for(i =1; 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;

sprintf(szOutString, "Number of 'e's: %d", number);


TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;

/* Inform Windows painting is complete */


EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM__CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */
414 Peter Norton’s QuickC for Windows

Debug Example
niAih"
Number of ‘e's: 785

Figure 10-4. Errors.exe First Attempt.

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

Program Type Build Mode' OK


<f> Windows EXE1 O QuickWm EXE ® Debug
Cancel
O Windows DLL O DOS EXE O Release
Help
customize Build Options

Compiler... | Linker... | Resources...

Figure 10-5. QuickC for Windows’Project Dialog Box.


Debugging QuickC for Windows Programs 415

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:

1. Program execution has reached a certain line number.

2. A given expression has become true.

3. A given expression has changed.

4. A certain Windows message was received.

In our case, we’ll start by halting program execution at a certain location in


our program and taking a look around at various program variables. For
example, we can stop at this location where we’re just about to fill the output
string, szOutString:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.
hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);
buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
fclose(fptr);
for(i = 1; i < 20; i++){
szOutString[i]-buffer[i]; //fill szOutString
if(buffer[i] == 'e') number++; //count number of 'e's
}

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 |

Location: .127 | Cancel |


WV=d Prat:
.. ^ Help |
lly.pi

: "-rV'fh-
—w! :Srl-

Breakpoints:

Add

Delete

Oear At

Figure 10-6. QuickC for Windows’ Breakpoint Dialog Box.

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

expressions or variables; anything that yields a nonzero result is considered


TRUE.

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):

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.
hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);
buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
fclose(fptr);
for(i = 1; i < 20; i++){
szOutString[i]=buffer[i]; //fill szOutString
if(buffer[i] == 'e') number++; //count number of 'e's
}

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 |

Location: .127 Cancel

Wnd F'roc .i a Help j


E'.xpreiU-ion
1
Length

Breakpoints:
127"
(Add] |

Clear All

Figure 10-7. Setting a Breakpoint While Debugging.

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

MSDOSHnd = OpenFi1e((LPSTR) szFileName, &of, OF


f dopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
fclose(fptr);

i ++)
szOutString[i]=buf fer[l];
if(buffer[i] == 'e') number++;
>
TextOut(hDC, 0, 0, (LPSTR) szOutString, strlen{
cYloc += cyChar;

sprintf(szOutString, "Number of 'e's: Zd", numb


—0-c.Y lor-( T FSTR ' i r~'h ti.-t ■-■ t t-

JT

Figure 10-8. A Set Breakpoint.


Debugging QuickC for Windows Programs 419

u QuickC for Windows [break] - ERRORS.EXE dd


File Edit View Project Run Debug Tools Options Window Help
Fonts: Courier V] Pts: 10
a i i ^ 1
lr-1- 3
<1> C:\QCWIN Debug Example

MSDOSHnd = OpenFil
f dopen (MSDOSHnd.. 11
fread(buffer, 1, 2
fclose(fptr);

szOutString
if(buf fer[i

Figure 10-9. A Debugging Session.

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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.

—> hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);

buffer = LocalLock(hLocal):

—> MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);

fdopen(MSDOSHnd, "r");

—> fread(buffer, 1, 20, fptr);

—> fclose(fptr);
for(i = 1; i < 20; i++){
420 Peter Norton’s QuickC for Windows

szOutString[i]=buffer [i] ; //fill szOutString

if(buffer[i] == 'e') number++; //count number of 'e's

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

Figure 10-10. A Debug Watch Window.


Debugging QuickC for Windows Programs 421

Figure 10-11. The Quickwatch Dialog Box.

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.

There’s another way of examining a variable or expression without adding it


to the watch window, and that’s by using the Quickwatch... item in the Debug
menu. To do that, simply position the caret over the expression or variable
you want to examine, and select this item. (A quicker way of selecting this item
is to click the button with the eyeglasses icon in the upper right-hand corner
of the QuickC for Windows window, or by using Shift F9.) When you do, the
Quickwatch dialog box opens, as in Figure 10-11, giving us the same informa¬
tion as we’ve seen in the Watch window.

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:

case WM_PAINT: /* code-for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
422 Peter Norton’s QuickC for Windows

hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

//Following code has bugs in it.

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
—> fread(buffer, 1, 20, fptr);
fclose(fptr);

for(i = 1; i < 20; i++){


szOutString[i]=buffer[i]; //fill szOutString
if(buffer[i] == 'e') number++; //count number of 'e's
}

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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

//Following code has bugs in it.

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fptr = fdopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
fclose(fptr);

for(i =1; 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;

sprintf(szOutString, "Number of 'e's: %d", number);


TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString))
cYloc += cyChar;

LocalUnlock(hLocal);
LocalFree(hLocal);
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
DestroyWindow(hWnd);
424 Peter Norton’s QuickC for Windows

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.

E QuickC for Windows [break] - ERRORS.EXE 33


| File Edit View Project Run Debug Tools Options Window Help
I Fonts: Courier _±] Pts: 10 _±j \f|&H r- H
1 r::.~ ....». 1 . .... ..-1-,i ■
<1> C:\QCWIN\BIN\ERRORS.C

<2> Watch
+buffer = OxlBAD:OxlADO "This is a test\r\n"

Break at location breakpoint. 00002 059

Figure 10-12. Watching Our Data Buffer.


Debugging QuickC for Windows Programs 425

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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.
hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);
buffer = LocalLock(hBocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
426 Peter Norton’s QuickC for Windows

fclose(fptr);

—> for(i = 1; i < 20; i++){

—> szOutString[i]=buffer[i]; //fill szOutString

—> if(buffer[i] == 'e') number++; //count number of 'e's

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;

sprintf(szOutString, "Number of 'e’s: Zd


00128 020

Figure 10-14. Examining szOutString in Debug Mode.


Debugging QuickC for Windows Programs 427

Figure 10-15. Filling szOutString in Debug Mode.

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) :

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.
hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);
buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, " r") ;
fread(buffer, 1, 20, fptr);
428 Peter Norton’s QuickC for Windows

fclose(fptr);

—> for(i =1; i < 20; i++){


szOutString[i]=buffer[i]; //fill szOutString
if(buffer[i] == 'e') number++; //count number of 'e's
}

We can fix that, giving us a new window procedure 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 *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 */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
Debugging QuickC for Windows Programs 429

hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

//Following code has bugs in it.

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


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;

sprintf(szOutString, "Number of 'e's: %d", number);


TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString))
cYloc + = cyChar;

LocalUnlock(hLocal);
LocalFree(hLocal);
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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 */
430 Peter Norton’s QuickC for Windows

Figure 10-16. Filling szOutString Correctly.

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:

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
/* Included in case the background is not a pure color */
SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.
hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);
Debugging QuickC for Windows Programs 431

Figure 10-17. Examining Variables in the Debugger.

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;

sprintf(szOutString, "Number of 'e's: %d", number);


TextOut(hDC, 0, cYloc, (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

Figure 10-18. Uninitialized Counter Bug.

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 */

case WM_MOVE: /* code for moving the window */


break;
Debugging QuickC for Windows Programs 433

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);

//Following code has bugs in it.

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fdopen(MSDOSHnd, "r");
fptr = fread(buffer, 1, 20, fptr);
fclose(fptr);

—> 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;

sprintf(szOutString, "Number of 'e's: %d", number);


TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;

LocalUnlock(hLocal);
LocalFree(hLocal);
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps);
break; /* End of WM_PAINT */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0); /* Quit the application */
break;

default:
434 Peter Norton’s QuickC for Windows

/* 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 */

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.

Now our program functions as intended, as shown in Figure 10-19. We’ve


debugged it; the full version of the program appears in Listing 10-1.

QuickC for Windows Irunj - ERR


Debug Example
ERRORS.EXE
I ▼ | * [ools
ools Options Window
na
Help
This is a testll
Number of 'e‘s: 1
B pm pm

[i];
number++;

cYloc = 0;
TextOut(hDC, 0, 0, (LPSTR) szOutString, strler
cYloc += cyChar;

Sit
00128 026

Figure 10-19. Our Debugged Program.

Listing 10-1. Debugged Version of Bugs.c.

/* QuickCase:W KNB Version 1.00 */


#include "ERRORS.h"

int PASCAL WinMain(HANDLE hlnstance, HANDLE hPrevInstance, \


LPSTR IpszCmdLine, int nCmdShow)
{
Debugging QuickC for Windows Programs 435

Listing 10-1. (continued)

/***********************************************************************/

/* HANDLE hlnstance; handle for this instance */


/* HANDLE hPrevInstance; handle for possible previous instances */
/* LPSTR IpszCmdLine; long pointer to exec command line */
/* int nCmdShow; Show code for main window display */
/***********************************************************************/

MSG msg; /* MSG structure to store your messages */


int nRc; /* return value from Register Classes */

st rcpy(s zAppName, "ERRORS");


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 */
"Debug Example", /* Window's title */
WS_CAPTION /* Title and Min/Max */
WS_SYSMENU /* Add system menu box */
WS_MINIMIZEBOX /* Add minimize box */
WS_MAXIMIZEBOX /* Add maximize box */
W S_THICKFRAME /* thick sizeable frame */
WS_CLIPCHILDREN /* don't draw in child windows */
WS_OVERLAPPED,
CW_US EDEFAULT, 0 /* Use default X, Y */
CW_US EDEFAULT, 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 */

(continued)
436 Peter Norton’s QuickC for Windows

Listing 10-1. (continued)

if(hWndMain == NULL)
{
Loadstring(hlnst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
return IDS_ERR_CREATE_WINDOW;
}

ShowWindow(hWndMain, nCmdShow); /* display main window */


»

while(GetMessage(&msg, NULL, 0, 0)) /* Until WM_QUIT message */


{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

/* Do clean up before exiting from the application */


CwUnRegisterClasses() ;
return msg.wParam;
} /* 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)
{
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

Listing 10-1. (continued)

OFSTRUCT of;

switch (Message)
{
case WM_CREATE:
hDC = GetDC(hWnd);
GetTextMetrics(hDC, &tm);
cyChar = tm.tmHeight;
ReleaseDC(hWnd, hDC);
break; /* End of WM_CREATE */

case WM_MOVE: /* code for moving the window */


break;

case WM_SIZE: /* code for sizing client area */


break; /* End of WM_SIZE */

case WM_PAINT: /* code for the window's client area */


/* Obtain a handle to the device context */
/* BeginPaint will sends WM_ERASEBKGND if appropriate */
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

/* Included in case the background is not a pure color */


SetBkMode(hDC, TRANSPARENT);
//Following code has bugs in it.

hLocal = LocalAlloc(LMEM_MOVEABLE, 1000);


buffer = LocalLock(hLocal):
MSDOSHnd = OpenFile((LPSTR) szFileName, &of, OF_READ);
fptr = fdopen(MSDOSHnd, "r");
fread(buffer, 1, 20, fptr);
fclose(fptr);

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

Listing 10-1. (continued)

sprintf(szOutString, "Number of 'e's: %d", number);


TextOut(hDC, 0, cYloc, (LPSTR) szOutString, strlen(szOutString));
cYloc += cyChar;

LocalUnlock(hLocal) ;
LocalFree(hLocal) ;
/* Inform Windows painting is complete */
EndPaint(hWnd, &ps) ;
break; /* End of WM_PAINT * */

case WM_CLOSE: /* close the window */


/* Destroy child windows, modeless dialogs, then, this window */
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));

/* 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;
Debugging QuickC for Windows Programs 439

Listing 10-1. (continued)

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 */
/* */
/************************************************************************ 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 •

'

' &U /Ai !>.{»•• iaofl-:


Index

accelerators. See menu accelerators bugs.c, 434-39


active window, 76 buttons, inactive, 244
ALT keys, 135-37
anNameStrlen[ ], 153-54, 347 calc.dig, 238-39
anNumberStrlen [ ], 152-54, 347 CALCMsgProc ( ), 239-42
AppendMenu( ), 154-55, 156 calculator, 236-52
application icon, 46, 47 listing of mycalc.c, 244—51
Arrange menu, in Dialog Editor, 268 call-back procedures, 73
arrays, 367-71 Cancel button, 211, 217-19
ASCII code, 61 caption bar, of currently active window, 76
asNameStringf ] [ ], 153-54, 294, 347 caret, 76
asNumberString[ ] [ ], 152-53, 294, 347 CB_ADDSTRING, 314
assertions, 407-11 CB_DELETESTRIN G, 314
asterisk (*), after a file’s name in QCWin, 11 CBJDIR, 314,341
atoi( ), 242 CB_FINDSTRING, 314
CB_GETCOUNT, 314
background color, 47, 48 CB_GETCURSEL, 314
background mode, 30 CB_GETEDITSEL, 314
BeginPaint( ), 28, 84, 174 CB_GETITEMDATA, 314
bEllipse flag, 187 CB_GETLBTEXT, 314
bEnteringNumber, 152,153 CB_GETLBTEXTLEN, 314
BLACK_BRUSH, 184-85 CBJNSERTSTRING, 314
BLACKJPEN, 181 CB_LIMITTEXT, 314
bounding colors, 188 CBN_DBLCLK, 313
breakpoints, 415-19 CBN_DROPDOWN, 313
brush, 47^48 CBN_EDITCHANGE, 313
brushes, 180 CBN_EDITUPDATE, 313
creating, 185-86 CBN_ERRSPACE, 313
for drawing rectangles, 184-85 CBN_KILLFOCUS, 313
buffers, circular, 392 CBN_SELCHANGE, 313

441
442 Peter Norton’s QuickC for Windows

CBN_SETFOCUS, 313 database program, 290-314


CB_RESETCONTENT, 314 adding Open... and Save As... dialog boxes to,
CB_SELECTSTRING, 314 339-40
CB_SETEDITSEL, 314 listing ofmydb.c, 304-13, 351-66
CB_SETITEMDATA, 314 DBMsgProc( ), 294
CBSHOWDROPDOWN, 314 debugging, 407-39
character buffer, 84-85 breakpoints and, 415-19
chdir( ), 320 executing code line by line and, 426-27
checking menu items, 138-47 interactive, 411-14
CheckMenuItem( ), 141 szOutString and, 425-33
CheckOnlyMenuItem( ), 171-74 default focus, 77
child windows, 33, 53 Default option, for push buttons, 217
choosing, 5
DefWindowProc( ), 35, 62-63, 68, 123
circular buffers, 392
DeleteObject( ), 278
client area, 4-5
DestroyCaret( ), 95
color background, 47, 48 DestroyWindow( ), 33, 98-99
COLORREF, 140 development environment, 10
colors device context handles, 83-84
bounding, 188 DialogBox, 223-25
filling figures with, in paint program, 188-! dialog boxes, 119, 209-314
selecting pens and, 180-83 calculator example, 236-52
COLORWINDOW+l, 48 listing of mycalc.c, 244-51
combo boxes, 313-14 listing of mycalc.rc, 251-52
control panel application, 267-90 cancel buttons in, 211
linking, 271
creating multiple, 338-39
listing of mypanel.c, 282-90
designing, 213-36
controls, 76-77, 209
.dig files, 219-20
coordinates, 85
linking dialog box to File
mouse cursor, 105
menu, 220-23
screen area, 106
listings, 228-35
CreateBrushIndirect( ), 186
modal, 221
CreateCaret( ), 95-7
OK and Cancel buttons, 217-19
CreateHatchBrush( ), 185
push buttons, 215
CreatePatternBrush( ), 186
resizing, 214
CreatePen( ), 181, 182
CreateSolidBrush( ), 185-86, 279 title, 214
CreateWindow( ), 52-58 general steps for adding, 236
window style flags under Windows, 53-55 message boxes and, 210-13
creation parameters, 56 modal, 211
CS_BYTEALIGN CLIENT, 45 notepad example, 252-65
CS_BYTEALIGNWINDOW, 44, 45 Open..., 315-34
CS_CLASSDC, 45 Dialog Editor, 209, 213-220. See also dialog boxes,
CS DBLCLKS, 45,105 designing
CSGLOBALCLASS, 45 Arrange menu in, 268
CS HREDRAW, 44-45 listboxes in, 291-94
CS_N OCLOSE, 45 toolbox of, 213, 214
CSOWNDC, 45 DIALOGMsgProc( ), 223-27
CS PARENTDC, 45 DispatchMessage( ), 63
CS_SAVEBITS, 45 DKGRAYBRUSH, 184
CS_VREDRAW, 44-45 .dig files, 219-20
cursor, 76 .dll files, 11
Cut and Paste buttons, 252, 255 DOS C programs, running under
CwUnRegisterClasses( ), 64 Windows, 9-13
CWJJSEDEFAULT, 56 DOS .exe files, 10
Index 443

drawing GetDC( ), 84, 174, 176-77


ellipses, 187-88 GetDlgItem( ), 275
freehand, 175-79 GetDlgItemText( ), 242, 275
lines, 179-83 GetFocus( ), 77
rectangles, 183-86 GetKeyState( ), 79-80
drawing mode of pens, 191-92 GetMenu( ), 138
dropdown menus. See menus GetMessage( ), 60-62
GetStockObject( ), 139, 141
Edit Field Styles dialog box, 252 pens, 180-81
ellipses, drawing, 187-88 rectangles, 184—85
EM (edit message) messages, 256-57 GetSubMenu( ), 150-51
EnableMenuItem( ), 141 GetSystemMetrics( ), 86-88
EndDialog( ), 225 GetTempFile( ), 349
EndPaint( ), 31-32, 84 GetTextExtent( ), 96
error messages, 49 GetTextMetrics( ), 95
creadng a window and, 57-58 GetWindowWord( ), 276
event driven programming, 6-7, 39 GlobalAlloc( ), 351,409
.exe files, 10-12 GlobalFree( ), 351, 409
exiting the application, 63-64 GlobalLock( ), 351
Exit item GlobalUnlock( ), 351,409
adding to File menu, 131-35 Graphical Development Environment
in File menu, 4 See development environment
extended memory, 3 Graphical User Interfaces (GUIs), 2
GRAY.BRUSH, 184
FI key, as Help key, 77 graying out menu items, 137, 142-47
fclose( ), 325 GWW_HWNDPARENT, 276
fdopen ( ), 321-24, 346-47 GWW_ID, 276
file attribute, 319
File menu, 4, 118 handles, 40
adding a, 119-20 hatch brush, 186
adding an Exit item to, 131-35 hatch styles, 186
Open... item in, 315-34 hDC handle, 29-30
QCWin’s, 10-11 hello.c, 9-12, 14-23. See also WinMain ( )
files, 315-16 listing of, 17-23
Open... dialog box, 315-34 WndProc( ) for MMM, 000
file specification, 318-19 hello.def, 72
FILE structure, 321 hello.exe, 72
FloodFill( ), 188-90 hello.h, 39
focus, 76-77 IDS_ERR_REGISTER_CLASS in, 50-51
default, 77 program variables defined in, 41
Font menu, 138—39 hello.mak, 73
fonts, current, 95 hello.re, 50, 51,69
fopen( ), 321 hello.win, 15-17, 72
fopen.c, 326-34 HELLO project, 73-74
fopen.h, 334 HideCaret( ), 95
fread( ), 350 hlnst, 41
freehand drawing, 175-79 hlnstance, 40, 41,46
fwrite( ), 349 HIWORD( ), 105
HMENU, 138
Generate item, in Build menu, 122 HOLLOW_BRUSH, 184
Generation option, in QuickCase:W, 15-17 horizontal scroll, automatic, 252
GetBkColor( ), 140, 185 hPrevInstance, 40
GetClassWord( ), 278 HS_BDIAGONAL, 186
GetClientRect( ), 86 HSCROSS, 186
444 Peter Norton’s QuickC for Windows

HSDIAGCROSS, 186 LB ADDSTRIN G, 298


HSFDIAGONAL, 186 LB_DELETESTRING, 298
HS_HORlZONTAL, 186 LB_DIR, 317-19
HSVERTI CAL, 186 LB_FINDSTRING, 298
Hungarian notation, 38^40 LB_GETCURSEL, 298, 321
HWND, 41 LB_GETHORIZONTALEXTENT, 298
hWndMain, 58 LB_GETITEMDATA, 298
hWnd parameter, 24 LB_GETITEMRECT, 298
LB_GETSEL, 298, 301
icons LB_GETSELCOUNT, 298
program, 205-7 LB_GETSELITEMS, 298
IDCARROW, 46-47 LB_GETTEXT, 298, 302, 321
IDC_CROSS, 47 LB_GETTEXTLEN, 298
1DCJBEAM, 47 LB_GETTOPINDEX, 298
IDCICON, 47 LBJNSERTSTRING, 297-99
IDC SIZE, 47 LBN_DBLCLK, 292, 293, 300
IDC_SIZENESW, 47 LBN_ERRSPACE, 292
IDC_SIZENS, 47 LBN_KILLFOCUS, 292
IDC_SIZENWSE, 47 LBN_SELGANCEL, 292
IDCSIZEWE, 47 LBN_SELCHANGE, 292
IDC_UPARROW, 47 LBN_SETFOCUS, 292
IDC_WAIT, 47 LB_RESETCONTENT, 298
ID ^APPLICATION, 46 LB_SELITEMRANGE, 298
IDM_F_ADDNAME, 150,155-56 LB_SETCOLUMNWIDTH, 298
IDMFDIALOGBOX, 221-22 LB_SETCURSEL, 298
IDM_F_EXIT, 133,170 LB_SETHORIZONTALEXTENT, 298
IDM_F_HELLO, 123, 129,137 LB_SETITEMDATA, 298
IDMFNEW, 170 LB_SETSEL, 298
IDM_F_SYSTEMFIXEDFONT, 139 LB_SETTABSTOPS, 298
LB_SETTOPINDEX, 298
IDS_ERR_CREATE_WINDOW, 57
lines, drawing, 179-83
IDS_ERR_REGISTER_CLASS, 50-51
Image Editor, 47 freehand, 177
Initial State option, 119, 120 LineTo( ), 177
insertion point (caret), 76, 151 linked lists, 388-92
adding a, 94-99 “Link to” dialog box, 119, 120
listboxes, 267-68, 290-313
Insert Menu Item dialog box, 119, 120
displaying filenames in, 317-18
instance handles, 40
InvalidateRect( ), 156 options for, 202-3
invalid rectangle, 27-31 uses of, 267-68
listbox messages, 297-99
key.c, 82 list box notification, 292-93
adding a character caret to, 94-99 LoadCursor( ), 46
LoadIcon( ), 46
listing of, 88-94
LoadMenu( ), 51
key.mak, 82
LoadString( ), 50, 51
key.win, 82
LocalAlloc( ), 351,408
key2.c
LocalFree( ), 351,408
listing of, 100-4
LocalLock( ), 351, 408
with mouse input title bar, 108-9
LocalUnlock( ), 351, 408
key2.win, 108
long pointer to a command
keyboard input, 75-104
line, 40-41
adding a character caret, 94-99
LOWORD( ), 105
conventions of, 76-77
IParam, 24, 26, 78
reading character messages, 81-94 in WM_CHAR message, 81
reading keystroke messages, 77-81 lpfnWndProc, 45
support for using Windows with, 5 IpszCmdLine, 40-41
keystroke messages, reading, 77-78 LTGRAYBRUSH, 184
Index 445

MAKELONG( ) macro, 256-57 messages, 23—26. See also specific messages,


MAKEPOINT( ), 105 WM_CHAR
.mak files, 74 nonclient area, 106
Maximize boxes, 3-4 passing on to Windows, 34-35
MBABORTRETRYIGNORE, 212 message structure, 60
MB_APPLMODAL, 211 MF_ENABLED, 138
MB_DEFBUTTON, 3 Minimize boxes, 3-4
MB_DEFBUTTON 1, 211 MK_LBUTT ON, 176
MB_DEFBUTTON 2, 212 MK_MBUTTON, 176
MBJCONASTERISK, 3 MK_RBUTTON, 176
MB JCONEXCLAMATION, 211 mouse, 6
MB_ICONHAND, 212 determining presence of, 88
MB_I CON INFORMATI ON, 212 mouse cursor, 46
MB-ICONQUESTION, 212 mouse input, 104-16
MB-ICONSTOP, 212 listing of mouse input application, 112-16
MB-OK, 212 virtual “key” codes, 79
MB_OKCANCEL, 211 MoveTo( ), 177
MB_RETRYCANCEL, 212 MR_GRAYED, 138
MB_SYSTEMMODAL, 211 MS-DOS Executive, 3
MB-YESNO, 212 MSG structure, 60, 61
MB-YESNOCANCEL, 212 mycalc.c, 244-51
memory mycalc.h, 243-44
extended, 3 mycalc.rc, 251-52
saving, 370-71 mydb.c, 304-13, 351-66
menu.c, listing of, 125-29 Mydlg.c, 228-35
menul.c, 122 mydlg.h, 235
listing of, 142^17 mydlg.rc, 235-36
menu 1.mak, 119,122 mypad.c, 254, 258-64
menu 1.re, 129-30 mypad.mak, 254
listing of, 147^18 mypad.win, 252
menul.win, 122 mypanel.c, 282-90
menu accelerators, 135-37
menu bar, 4, 117 naming convention of Windows variables, 38-39
menu items nCharacters static integer, 84-85
accelerators for, 135-37 nCmdShow, 41,58-59
adding, at run-time, 148-65 nCwRegisterClasses( ), 42-52, 130
blanking a window, 156-57 nCwUnRegisterClasses( ), 42
nonclient area, 5
listing ofphone.c, 159-65
nonclient area messages, 106
ALT keys for, 135-37
nonmodal dialog box, 63
checking, 138—47
notepad, 252-65
graying out, 137, 142-47
Cut and Paste buttons, 252, 255
menus
listing of mypad.c, 258-64
adding, 118-35
multi-line and vertical scroll bar features, 252
Exit item, 131-35
notepad.dlg, 252, 256, 265
File menu, 119-20
N OTEPADMsgProc ( ), 254
flush right in the menu bar, 130
NULL_BRUSH, 184,185
initial state of menu, 119 NULL_PEN, 181
listing of menu.c, 125-29
switch statement, 122-23 objects. See stock objects
conventions in, 117-18 OEM scan code, 78
MENU structure, 130 OF_CANCEL, 323
MessageBox( ), 52, 210-13 OF_CREATE, 323, 345
message boxes, 210-13 OF_DELETE, 322, 323
message loop, 60-65 OF_EXlST, 323
message queue, 39 OF_PARSE, 323
446 Peter Norton's QuickC for Windows

OFPROMPT, 322, 323 PSDASH, 181


OF_READ, 322, 323 PS_DASHDOT, 181
OF_READWRITE, 323 PS_DASHDOTDOT, 181
OF_REOPEN, 322, 323 PSDOT, 181
OF_SHARE_COMPAT, 323 PSJNSIDEFRAME, 181,182
OF_SHARE_DENY_NONE, 323 PS_NULL, 181
OF_SHARE_DENY_READ, 323 PS_SOLID, 181,182
OF_SHARE_DENY_WRITE, 323 push buttons
OF SHARE EXCLUSIVE, 323 adding, 215
OFSTRUCT structure, 322 ID number of, 215
OF_VERIFY, 323 options available for, 217
OF_WRITE, 323
OK button, 217-19 QuickCase:W, 14-17, 23-31, 37
opaque mode, 30 Build menu, 15
Open... dialog box, 315-34 comments provided by, 15
Save As... dialog box and, 336-37 starting, 14
OpenFile( ), 321, 322, 345-46
QuickC for Windows
OPENMsgProc( ), 340
as box of tools and resources, 1
Graphical Development Environment¬
paintc, 169, 196-205. See also paint program
s'development environment
painth, 173
major steps to writing an application in, 8
paint program, 167-207
Quickwatch... item in the Debug
drawing ellipses in, 187-88
menu, 421
drawing lines in, 179-83
QuickWin .exe files, 11-13
drawing rectangles in, 183-86
filling figures with color in, 188-90
R2_BLACK, 192
freehand drawing in, 175-79
R2_COPYPEN, 192
listing of, 196-205
R2_MAS KN OT P EN, 192
program icon for, 205-7 R2_MASKPEN, 192
setting individual pixels in, 174-75 R2MASKPENNOT, 192
“stretching” graphics figures in, 191-96 R2_MERGEN OTPEN, 192
tools menu, 167-68, 171 R2_MERGEPEN, 192
paint structures, 28-29 R2_MERGEPENNOT, 192
PANELMsgProc( ), 271-72, 280-81 R2_NOP, 192
PANELsgProc( ), 273-74 R2_NOT, 192-93
parent windows, 56 R2NOTCOPYPEN, 192
pens R2_N OTMASKP EN, 192
drawing mode of, 191-92 R2.NOTMERGEPEN, 192
selecting colors and, 180-83 R2_NOTXORPEN, 192
pen styles, 181-82 R2_WHITE, 192
phone.c, 150-65 R2_XORPEN, 192
listing of, 159-65 .rc files, 50
pixels, setting, 174-75 reading character messages, 81-94
pointers, 368-71 Rectangle( ), 183-84
sorting techniques and, 371-88 rectangles, drawing, 183-86
POINT structure, 60,105 RF.CT structure, 86
pop-up menus. See menus redrawing a window, 27-28
PostQuit Message( ), 33-34 RegisterClass( ), 43-45, 48
PostQuitMessage( ), 64, 65 registering a window class, 42-52
prefixes, for variables, 38 resource files, 50
printf( ), 30 resources, defined, 50
program icon, for paint program, 205-7 resource script, 130
programming, event driven, 6-7, 39 RGB() macro, 174-75
Project dialog box, 11
project menu, 74 Save As... dialog box, 335-66
projects, 73-74 adding to database program, 339
Index 447

SAVEMsgProc( ), 340-43, 346 szResult, 242-43


SB_BOTTOM, 273 szString, 51,52
SB_CTL, 275, 276
SB ENDSCROLL, 273 TabbedTextOut( ), 88
SBHORZ, 275 tab key, 76-77
SB LINEDOWN, 273 tab order, 76-77
SB_PAGEUP, 273 tabs, 88
SB_THUMBPOSITION, 273, 276 termination code, 34, 65
SB_THUMBTRACK, 273 text box command messages, 256-58
SB_TOP, 273 textboxes, 217
SB_VERT, 275 TEXTMETRIC structure, 95-96
scan code, 78-79 TextOut( ), 30-31, 81, 84, 85, 140
screen area coordinates, 106 thumb, 5, 267
scroll bars, 4-5, 267 title bar, 3, 117
in control panel example, 268-90 Title dialog box, 82
labels for, 268-70 tmAveCharWidth, 96
range of, 274—76 tmHeight, 96
search.c, 400-4 tools menu, in paint program, 167-68, 171
searching, 392-405 Trace Into command, 426
selecting, 5 TranslateMessage( )
SelectObject( ), 141, 180 in message loop, 61-63
pens and, 182 reading keyboard input and, 80-81
SendDlgItemMessage( ), 255 transparent mode, 30
SendMessage( ), 275 “typematic” keystrokes, 78, 79
SetCaretPos( ), 95, 97, 98
SetClassWord( ), 278, 279 unregistering a window class, 64-65
SetDlgItemText( ), 226, 275 Update option, in QuickCase:W, 16
SetFocus( ), 77 User-Defined Code, 119
SetPixel( ), 177
SetSCrollPos( ), 276 virtual key codes, 79
SetScrollRange( ), 274-76 VK_A to VK_Z, 79
SetTextColor( ), erasing a string with, 140 VK_F1 to VK_F16, 79
shell sorts, 373-74 VK_LBUTTON, 79
VK_MBUTTON, 79
ShowCaret(), 95, 97
VK RBUTTON, 79
ShowWindow( ), 52, 58
sizing handles, 214
Watch window, 420-21
sorting, 371-88
WHITE_BRUSH, 48,184
sprintf( ), 376
WHITE_PEN, 181
SR_LINEUP, 273
window classes
stdlib.h, 243
registering, 42-52
Step Over command, 426
unregistering, 64-65
stock objects, 48. See also GetStockObject( )
window handles, 24
“stretching” graphics figures, 191-96
string tables, 51 windows. See also QuickCase:W
child, 33, 53
subdirectories, displaying, 319
submenus. See menus creating, 52-58
switch statement, 24 location and size of, 55-56
in WndProc( ), 69, 85-86 parent, 56
S W_SH O WMIN N OACTIVE, 41 parts of, 3-5
SW_SHOWNORMAL, 41 showing on the screen, 58-59
System Fixed Font, 139 size of, 86
system menu box, 3 Windows 3.0
System Metrics constants, 86-88 as Graphical User Interface (GUI), 2
szAppName, 41, 48, 52-53 history of, 2-3
szOutString, 376 introduction of, 3
debugging and, 425-33 preserving the feel of, 5-6
448 Peter Norton's QuickC for Windows

running pre-existing C programs in a window in, WM_NCMBUTTONDBLCLK, 106


9-13 WM_NCMBUTTONDOWN, 106
terminology used in, 3-4 WM_NCMBUTTONUP, 106
386-enhanced mode, 3 WM_NCMOUSEMOVE, 106
windows.h, 24, 40, 41 WM_NCRBUTTONDBLCLK, 106
Windows Enableltem, 137-38 WM_NCRBUTTONDOWN, 106
Windows .exe files, 11 WM_NCRBUTTONUP, 106
Windows programming, 6-8 WM_PAINT, 24-25, 27-28, 30-32, 60, 69, 71, 83, 84,
Windows SetPixel( ), 174 156,170, 205
window style flags, 53-55 WM_QUIT, 33, 60, 62, 64, 65
.win files, QuickCase:W and, 15,16 WM_RBUTTONDBLCLK, 104
WinMain( ), 39-59 WM_RBUTTONDOWN, 104,105, 106
creating a window in, 52-58 WM_RBUTTONUP, 104
exiting the applicadon in, 63-65 WM_SETFOCUS, 77, 99
listing of, 65-68 WM_SIZE, 24, 26, 60, 69, 70, 83
message loop in, 60-65 WM_SYSKE YD OWN, 77-78
message queue and, 39 WM_SYSKEYUP, 77-78
registering a window class in, 42-52 WM_VS CRO LL, 272, 276
showing the window on the screen, 58-59 WNDCLASS structure, 43^19
WinMain( ) function, 23 WndProc( ), 23-35, 44-46, 68-73
winstub.exe, 72 exiting the application and, 63-64
WM_BU 1 TONUP, drawing ellipses and, 187 listing of, 70-71
WM_CHAR, 61, 75, 80-84 message loop and, 62-63
displaying a caret and, 97-99 reading character messages and, 82-83
WM_CLOSE, 32-34, 63, 69, 71, 83, 133-34, 170 switch statement in, 69, 85-86
destroying a caret with, 98-99 word wrap, automatic, 252
WM_COMMAND, 123-24,133 wParam, 24, 26, 78
WMCREATE, 24, 26, 56, 69, 70, 83 WS_CAPTION, 53,54
creating a caret in, 95-97 WS_CHILD, 54
WM_HSCROLL, 272 WS_CHILDWINDOW, 54
WM_HSCROLL message, 26 WS_CLIPCHILDREN, 53,54
WMJNITDIALOG, 281 WS_CLIPSIBLINGS, 54
WMJNITIDIALOG, 274-75, 277 WS_DISABLED, 54
WMKE YD OWN, 24, 61,77-81 WS_DLGFRAME, 54
WM KEYUP, 26, 77, 81 WS_EX_D LGMO DALFRAME, 54
WM_K1 LLFOCUS, 77, 99 WS_GROUP, 54
WM_LBUTT ON DB LCLK, 104, 105 WS_HSCROLL, 54
WMLBUTTONDOWN, 104, 105 WS_I CONIC, 54
drawing lines and, 179 WS_MAXIMIZE, 54
freehand drawing and, 177 WSMAXIMIZEBOX, 53,54
WM_LBUTTON message, 26 WS_MINIMIZE, 54
WM_LBUTTONUP, 104,105, 106 WSMINIMIZEBOX, 53,54
drawing lines and, 179 WS_0VERLAPPED, 53, 54
drawing rectangles and, 184 WS_OVERLAPPEDWINDOW, 54, 55
WM_MBUTTONDBLCLK, 105 WS_POPUP, 54
WMMBUTTONDOWN, 105 WSPOPUPWINDOW, 54
WMMBUTTONUP, 105 WS SIZEBOX, 54
WMMOUSEMOVE, 60, 104, 105-6 WS_SY5MENU, 53, 54
WMMOUSEMOVE WS_TABSTOP, 54
freehand drawing and, 175-78 WS_THICKFRAME, 53, 54
“stretching” graphics figures and, 191-96 WS_TILED, 54
WM MOVE, 24, 26, 69, 70, 83 WS_TILEDWINDOW, 54
WM_N CLBUTT ON DB LCLK, 106 WS_VISIBLE, 54
WMNCLBUTTONDOWN, 106 WSVSCROLL, 54
WMNCLBUTTONUP, 106
CUSTOMER SUPPORT

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 directly from S & S.


• Diskette replacement when applicable.
• Purchase of future product upgrades at special prices.
• Subscriptions to Hint Books and newsletters where applicable.

Software Support

S & S will provide support to registered owners. Our technical support number is (212) 373-
7212 or FAX (212) 373-8192.

Mail-in Support Service—Registered owners may write to us with questions. We will


respond in writing. There is no additional charge for this service.

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 Customer Support

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.

A. if ordering within thirty days of purchase


If a diskette is reported defective within thirty days of purchase, a replacement diskette will be provided free of charge.
The back of this card must be totally filled out and accompanied by the defective diskette and a copy of the dated sales
receipt. In addition, please complete and return the Limited Warranty Registration Card.

B. If ordering after thirty days of purchase but within one year


If a diskette is reported defective after thirty days, but within one year of purchase and the Warranty Registration Card
has been properly filed, a replacement diskette will be provided to you for a nominal fee of $5.00 (send check or money
order only). The back of this card must be totally filled out and accompanied by the defective diskette, a copy of the
dated sales receipt, and a $5.00 check or money order made payable to Simon & Schuster, Inc.

C. If ordering a 3.5 inch replacement disk


If you wish to order a 3.5-inch disk for this product, please complete the back of this card and mail it with your original
5.25-inch diskettes along with a nominal fee of $5.00 to cover shipping and handling (send check or money order only).
In addition, please complete and return the Limited Warranty Registration Card.

QuickC for Windows


LIMITED WARRANTY REGISTRATION CARD
In order to preserve your rights as provided in the limited warranty, this card must be on file with Simon & Schuster within thirty days of purchase.

Please fill in the information requested:


NAME__PHONE NUMBER ( ) _
ADDRESS__

CITY_STATE_ZIP_
COMPUTER BRAND & MODEL _ DOS VERSION_MEMORY_K

Where did you purchase this product?


DEALER NAME? _ PHONE NUMBER ( ) __
ADDRESS __
CITY__STATE_ZIP__
PURCHASE DATE _ PURCHASE PRICE_
How did you learn about this product? (Check as many as applicable.)
STORE DISPLAY_SALESPERSON_MAGAZINE ARTICLE_ADVERTISEMENT_

OTHER (Please explain)_


How long have you owned or used this computer?
LESS THAN 30 DAYS_LESS THAN 6 MONTHS_6 MONTHS TO A YEAR_OVER 1 YEAR_

What is your primary use for the computer?


BUSINESS_PERSONAL __EDUCATION_OTHER (Please explain)_

Where is your computer located?

HOME_OFFICE_SCHOOL _OTHER (Please explain)_

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 within 30 days. I have enclosed my original diskette


and a copy of the dated sales receipt. ISBN 0-13-749565-X

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

College Marketing Group


50 Cross Street
Winchester, MA 01890

ATTN: CHERYL READ


CUT TO OPEN

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.

The New Peter Norton Microcomputer


Libraries from Brady
The books in Peter Norton’s Hardware Library guide you to an
insider’s grasp of your computer and the way it works. This series
includes such bestselling classics as Inside the IBM PC, Inside the
Apple Macintosh, and rIhe Hard Disk Companion. Peter Norton’s
Programming Library offers books and book/ disk utilities for
readers at all levels of expertise—beginning to intermediate to
advanced. They focus on creating programs that really work and
offer the hottest tips and techniques in the industry. For a direct,
accessible, no-nonsense approach to performance computing,
look to Brady Publishing’s Peter Norton Libraries.
Brady Books Prentice Hall Computer Publishing

ISBN 0-13-747569-1

9780137475 698
2016-02-12 9:21
22 9780137475698
jjICKC FOR

You might also like