Borland®
Shop
Products Downloads Services Support Partners News & Events Company Community
Borland C++
Books
Awards
Documentation

Turbo Assembler
Resource Workshop

Borland C++ Support
Pre-Sales/Install Support

C++Builder

 Mastering Windows Programming with Borland C++4, Chapter 1

Chapter 1

Introducing Windows Programming Whether you're a seasoned veteran or just getting started in Windows programming, it's tough to master the techniques behind features that users take for granted. To be competitive, modern Windows software must take every possible advantage of multitasking, 32-bit code generation, graphics, custom and VBX controls, toolbars, status lines, and a host of other exotic components. This book can help. Borland C++ 4 (BC4) and the ObjectWindows class library 2.0 (OWL) are designed to help you create reliable 16- and 32-bit Windows applications without wasting months of your time just getting up to speed. In the coming chapters, you learn how to use BC4, and you focus on ObjectWindows techniques for performing tasks that, at the moment, might seem impossibly complex--like how to create a toolbar of icons, how to customize dialogs and controls, how to program a popup menu, how to print text and graphics, how to validate control data, and dozens of other topics.

This is a how-to book with a one-track purpose: to help you save time writing applications with features that Windows users demand. To help you get started, this chapter provides an overview of the ObjectWindows library. The next chapter covers the BC4 integrated development environment (IDE), preparing you for in-depth looks at Windows programming throughout the rest of the book.

In this chapter, you learn:

  • Different approaches to solving the complexity problem in Windows development.
  • Why a class library can simplify writing Windows software.
  • How to write a simple Windows program.
  • How to construct an application object.
  • Types of classes for Windows programming.
  • How to use BC4's header files.
  • The benefits of encapsulation and inheritance to Windows programming.

NOTE

Please feel free to use the sample listings in this book in any way you wish. You do not need special permission to incorporate my sample programs into your own compiled code.


The Complexity Problem
Writing Windows software in straight C or assembly language is possible, of course, but at what cost? Applications such as Microsoft Excel have consumed an estimated five hundred combined programmer years in development--an investment that lone developers and small software shops cannot afford. The solution for most Windows developers is to save time by using third-party libraries and development systems to build applications. The real question then becomes: Which of the many available software tools should you use? A few possible solutions follow.

The Straight C Approach
According to the popular press, C and Windows fit hand in glove. The truth, however, is that straight C was never designed to handle the event-driven, highly complex, world of Windows programming. For most developers, it's too time-consuming to write and debug reliable code written in C. As soon as you finish and test your application, some other programmer with more efficient tools might come along and scarf up your customers. How long can you wait to bring your code to market?

The Application Generator Approach
To save time, some developers turn to an application generator. Typically, these products create program source code files from selected menu commands, dialogs, controls, and other options. When the source file shell is finished, the developer fills in the blanks with the application's code. An application generator is a good way to create prototypes, database entry screens, and similar applications in a hurry. All generators, however, have two main failings. First, you still have to write the heart of your program. No application generator can do that for you. Second, when the time comes to upgrade your code, the generator may be unable to help. Experience teaches that automatically generated applications are usually rewritten from scratch at upgrade time. Can you afford to rewrite your program for every new release?


NOTE

BC4 includes the AppExpert application generator, which suffers from the same disadvantages discussed here. AppExpert might be useful in some cases, but I suggest you learn how to program Windows applications from scratch before using AppExpert. That way, you'll have a better appreciation for the generated code, and you stand a better chance of being able to reuse that programming for your program's upgrades. See Chapter 2, "Introducing Borland C++ 4," for more information about AppExpert.


The Visual Plug-and-Play Approach
The visual programming approach is highly appealing for some types of applications. Products such as Visual Basic and ToolBook offer plug-and-play objects that interpret instructions when selected, or are linked in some way to other objects and preprogrammed operations. Visual Basic is suitable for database entry screens, calendars, and similar small-scale utilities. For general-purpose application development, however, the plug-and-play approach doesn't cut the mustard. When performance matters (when doesn't it?), and when you cannot afford to be restricted by the limited range of capabilities offered in a visual programming system, you need powerful tools and a general-purpose development language such as C or C++.


NOTE

BC4 includes classes for interfacing with Visual Basic controls, also called VBX controls. With these classes, you can incorporate plug-and-play modules into Windows programs yet still use C++ as your development language. For more information on VBX controls, see Chapter 15, "Putting On the Finishing Touches."


The Class Library Approach
That brings us to what is probably the best approach for Windows programming--using C++ and a class library such as ObjectWindows. This solution offers programmers two key advantages over the preceding methods. One, C++ classes model the Windows architecture far better than straight C. A window class, for example, encapsulates data and functions. The class automatically takes care of internal requirements that you have to provide explicitly in C. In addition to encapsulation, by using response tables, class member functions can respond directly to Windows messages, simplifying the difficult task of writing code for an event-driven operating system.

Two, a class library such as OWL provides an application framework on which you build programs. Through inheritance, you derive new classes based on those that BC4 provides to which you add the capabilities you need. With C++, you reuse existing code, which saves development time, minimizes debugging, and improves your application's reliability.

OWL isn't the only Windows class library on the market. Some products such as the Microsoft Foundation Class Library provide wrapper classes that serve as stand-ins for Windows functions. Wrapper-class libraries make it possible to use C++ rather than straight C for Windows programming, but they offer little or no savings in development and debugging time. OWL has wrapper classes, and it also provides a framework that helps direct, mold, and shape application design.

Other products offer single-source compilation for different operating systems. You can purchase a library, for example, with identical classes that target the Macintosh and Windows operating systems. Unfortunately, you pay a high price in lost performance for this level of compatibility. Functions in multiplatform libraries must be translated into low-level subroutine calls for Macintosh and Windows, which wastes time. OWL makes direct calls to the Windows API (application programming interface). Many such calls are compiled as inline statements--not, in other words, as nested subroutines. Because of BC4's extensive use of inline code, using the library adds practically no appreciable runtime overhead to the final application. Many expert programmers claim that because a class library helps organize programs more efficiently, the final program usually performs better than straight code written from scratch. That's a difficult claim to prove, but those who are familiar with OWL generally confirm that using a class library does not necessarily degrade runtime performance, which you can expect with interpreted languages such as Visual Basic.


NOTE

OWL 2.0 is written to conform to the latest ANSI C++ standard, raising the possibility that future editions of OWL will be available for other operating systems. So far, OWL is available only for Windows, but it stands to reason that Borland didn't insist on OWL's ANSI C++ compatibility for no reason at all.


Say Hello to ObjectWindows 2.0
That's enough background for now. A sample program demonstrates some of the advantages of using a class library to program Windows applications. Listing 1.1, HOWL.CPP (Hello OWL), is the ObjectWindows equivalent of the "hello world" example traditionally used to introduce C programming. Compile and run the listing to say "Hello to OWL programming."


NOTE

There are two ways to compile and run HOWL.CPP. Using the BC4 integrated environment, switch to the HOWL directory, open the HOWL.IDE project, and press Ctrl+F9. Or, from a DOS prompt, change to the HOWL directory and type make, then use the File Manager to select HOWL.EXE. Compile and run all of this book's sample programs in the same way. Remember, to compile programs in the IDE, you must open their .IDE project files. If you have trouble compiling programs, see Appendix A, " compilation Instructions," for help.


Listing 1.1. HOWL.CPP.
#include <owl\applicat.h>

int
OwlMain(int argc, char* argv[]) {
  TApplication app("Hello World from OWL 2.0!");
  return app.Run();
}

Though simple-minded, HOWL.CPP demonstrates four key elements found in every Windows program:

  • An #include directive refers to a header file such as APPLICAT.H. There are many such headers located in the OWL subdirectory in the compiler's default include-file path (usually C:\BC4\INCLUDE). To use OWL classes, simply include the headers that declare them.
  • The OwlMain function receives the same two arguments that a standard C program's main function receives. Also like main, OwlMain returns an int value (written above the function name in the style that many C++ programmers prefer). Parameter argc of type int indicates the number of command line arguments passed to the program. Parameter argv, a character-pointer array, addresses those arguments as null-terminated strings. The expression argv[0] addresses the program's name, argv[1] addresses the first string argument, argv[2] addresses the second, and so on.
  • OwlMain defines an application object of the TApplication class. The application object encapsulates the entire application--there is only one TApplication object in a program. You can initialize the object with a string for the program's main window title, but as you will learn, there are other, and probably better, ways to construct application objects. The method shown here, however, is handy for quick demos and tests.
  • OwlMain calls the application's Run member function to activate the program's message loop. At this point, the program leaves its startup phase and enters normal operation. Chapter 3, "Starting Up and Shutting Down," discusses operations you can perform during an application's early moments, and also explains what happens when the program ends. The Run function returns an integer status value that OwlMain returns.

Unused Arguments
When you compile HOWL.CPP, you may notice two warning messages that tell you argc and argv are not used in OwlMain. Normally, the compiler tells you when a function defines but does not use an argument or variable. Not using a defined variable wastes memory, but not using a variable that has to be declared anyway because of OWL's design is a minor problem that you can safely ignore.

For your own functions, when you receive an unused argument warning, you probably should delete the unused parameter or other variable. When using supplied functions such as OwlMain, though, the arguments are passed to the functions whether the code uses the parameters or not. In that case, you have two options: ignore the warning or disable it by inserting this line above OwlMain:

#pragma argsused
The #pragma argsused directive disables the compiler's warning about unused arguments, but only for the next function. You can also disable the warning by surrounding selected function parameters with comment brackets:
int
OwlMain(int /*argc*/, char* /*argv*/[ ] )
That's usually better because it makes you account for the use of each parameter. The #pragma option affects all unused parameters and variables in the function. Sample programs in this book use both techniques.

A Shorter OwlMain It's possible to shorten OwlMain in HOWL.CPP to one statement, a technique you may see in other published OWL programs (most notably, for example, BC4's example OWL applications). OwlMain can create and execute a TApplication object, and call its Run member function, with this lone statement:

return TApplication("Hello World from OWL 2.0!").Run ( ) ;
To try the alternate code, replace OwlMain's two statements with that one. It constructs a temporary TApplication object on the stack, calls its Run member function, and returns that function's result. The object is automatically destroyed when OwlMain ends.

There's no advantage of the single-line method over that shown in Listing 1.1--I prefer the longer method of constructing objects and then using them. The single-line method is tricky, and it provides no practical benefits. Sample programs in this book use the longer, and more readable, technique shown in HOWL.CPP.

Module-Definition Files Windows programs are rarely composed of a single file such as HOWL.CPP. Even this simple example, for instance, uses a module-definition file, which specifies linker options that affect the type of module created, and that configure some module settings. Listing 1.2, HOWL.DEF, lists the sample program's module-definition file.


Listing 1.2. HOWL.DEF.


EXETYPE WINDOWS
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 4096
STACKSIZE 8192

BC4 does not require a module-definition file to create Windows applications and libraries. If you don't specify a .DEF file, the linker uses a default file, a fact the linker also warns you about. You also can select linker settings by using IDE commands.


NOTE

BC4's default module-definition file, DEFAULT.DEF, is located in the C:\BC4\LIB directory.


Even so, module-definition files have many possible settings--too many to explain fully in this introduction. Some options differ depending on whether you are creating a 16- or a 32-bit program, and many options won't make any sense until you learn about the associated programming, so I'll skip a complete overview here. For more information about module-definition files, look them up in the BC4 User's Guide. Following, however, are some notes about options you may want to use now.

The first line of a module-definition file for 16-bit applications (32-bit programs don't need this) is always:

EXETYPE WINDOWS
There is only one type of executable code file for 16-bit Windows, so there aren't any other options in the EXETYPE statement. The next line in the file is usually a CODE statement, which has the following syntax:
CODE
  [FIXED|MOVEABLE]
  [DISCARDABLE|NONDISCARDABLE]
  [PRELOAD|LOADONCALL]

The brackets indicate items that are optional. (All the ones shown here are optional, but of course, if you didn't include any settings, there'd be no reason to have a CODE statement.) The vertical bar means "or." So, you can specify a CODE segment to be either FIXED or MOVEABLE, but not both. The CODE statements options are:

  • FIXED--Segment remains at a fixed address.*
  • MOVEABLE--Segment can be moved to make room in memory.*
  • DISCARDABLE--Segment can be swapped out of memory to make room.
  • NONDISCARDABLE--Segment cannot be swapped.
  • PRELOAD--Segment is loaded into memory when application is executed.
  • LOADONCALL--Segment is loaded into memory only when an item in the segment is referenced.

NOTE
The FIXED and MOVEABLE settings have little meaning in the world of 386-enhanced, protected-mode execution, in which segments can be moved but still appear to be fixed for the purposes of calling functions, referring to variables, and so on. There's practically no reason to use the FIXED setting. You might as well make code segments MOVEABLE--they are anyway.


The 32-bit linker, TLINK32, recognizes only the PRELOAD and LOADONCALL code segment settings. It also recognizes two others:

  • EXECUTEONLY--Segment can be executed.
  • EXECUTEREAD--Segment can be executed and read (by another process, for example).
A DATA statement in a module-definition file specifies attributes for a program or library's data segments. The DATA statement has the following syntax:
DATA
  [NONE|SINGLE|MULTIPLE]
  [READONLY|READWRITE]
  [PRELOAD|LOADONCALL]
  [SHARED|NONSHARED]
  • NONE--No data segment (used only by DLLs).
  • SINGLE--Single data segment shared by all processes (the default for DLLs).
  • MULTIPLE--Multiple data segments (the default for executable code files).
  • READONLY--Data in segment can be read only, not changed.
  • READWRITE--Data in segment can be read and changed.
  • PRELOAD--Segment is preloaded automatically into memory.*
  • LOADONCALL--Segment is loaded into memory when referenced.*
  • SHARED--One copy of data segment is shared by all processes (the default for 16-bit DLLs).
  • NONSHARED--A copy of the data segment is loaded for each process (the default for applications and for 32-bit DLLs).

NOTE
32-bit applications linked by TLINK32 ignore the PRELOAD and LOADONCALL options.


A description statement inserts text into a code file, usually to embed a copyright notice. You may insert a DESCRIPTION statement such as the following in a module-definition file.

DESCRIPTION `Copyright (c) 1994 by Your Name'
HEAPSIZE and STACKSIZE statements set the size of the local heap and the program's stack. Only an executable code file has a stack--a DLL shares the host application's stack.

A STUB statement inserts a DOS program into the Windows .EXE code file. It is never used for a library. If you don't specify a STUB program, BC4 automatically inserts WINSTUB.EXE into the finished .EXE file. The default file is located in C:\BC4\BIN. From a DOS prompt, switch to that directory, and type WINSTUB to execute the program. On screen, you see the familiar message:

This program must be run under Microsoft Windows.
Actually, in this case, that's a lie because you just executed WINSTUB from DOS! Seriously, however, the stub is there just to do something if a user attempts to run a Windows program from DOS, which, of course, is a no-no. If you don't like the default message, write your own DOS .EXE file, and specify it in the module-definition file's STUB statement:
STUB mystub.exe

The stub is supposed to display a message and quit, but some programmers use this trick to create .EXE code files that can run the same program under DOS and Windows. Each part of the dual-purpose code file is still a separate program with its own copies of any library functions and so on.

Other module-definition options include a NAME statement for 32-bit applications, plus EXPORTS and IMPORTS statements for dynamic link libraries, and SEGMENTS for defining multiple segment options. With BC4, and especially when using the integrated development environment, there are easier methods to create 32-bit programs and libraries, so you may never need these settings.

The ObjectWindows Class Library
By the time you reach the end of this book, you will have met most of OWL's many classes, which encompass the following main areas:

  • Windows: The TWindow class provides a base-class interface to window elements such as dialog boxes, controls, child windows, and others.
  • Frame windows: The TFrameWindow class, derived from TWindow, is typically used to construct a program's main window object. A TFrameWindow can own a client window that performs the window's visual antics. (Programs that use a dialog as their main window benefit greatly from this arrangement.)
  • MDI windows: Three classes fully support the multiple document interface (MDI): TMDIFrame, TMDIClient, and TMDIChild. MDI child windows are instances of the TMDIChild class.
  • Graphics classes: The entire Windows graphics device interface (GDI) is encapsulated in a set of device-context classes such as TClientDC, TPrintDC, TPaintDC, and others. To draw in a window, or to print text and graphics, you simply construct a device-context object and call a member function in reference to that object.
  • GDI object classes: All objects such as pens, brushes, color palettes, fonts, icons, cursors, bitmaps, and others have associated GDI classes. A Windows pen is an object of the TPen class; a brush is a TBrush instance, and so on. To use these items, you first select them into a device context object. Next, you call class member functions that output graphics using the selected tools. Because the device context knows about the graphics objects it owns, the context automatically deletes objects when you are done using them, a feature that helps reduce memory leaks caused by failing to delete pens and brushes--common problems in conventional Windows programs.
  • Decorated windows: These classes simplify programming toolbars, status lines, and other window decorations. Define a window's layout with TLayoutWindow and TLayoutMetrics. Insert self-adjusting client windows with TDecoratedFrame and TDecoratedMDIFrame. Add tiled gadgets with TGadgetWindow and TGadget. All of these classes dress up windows with features especially appreciated by power users.
  • Dialog boxes: The TDialog class interfaces with Windows dialogs designed as resources with Resource Workshop or in resource script files. TDialog's data transfer mechanism makes it easy to copy information to and from dialog controls.
  • Common dialogs: These classes provide class interfaces for all common Windows dialogs. These classes include file choosers (TOpenSaveDialog, TFileOpenDialog, and TFileSaveDialog), a font selector (TChooseFontDialog), a color picker (TChooseColorDialog), a printing options interface (TPrintDialog), and three search-and-replace prompters (TFindReplaceDialog, TFindDialog, and TReplaceDialog).
  • Control classes: The TControl class constructs control objects that simplify communicating with control elements in windows. OWL 2.0 offers three types of controls: standard Windows controls, Widgets (custom sliders, for example, written entirely in C++), and decoration controls (toolbars and status lines). OWL 2.0 also supports 16- and 32-bit versions of Borland Windows Custom Controls (BWCC) as well as Microsoft 3-D controls.
  • Printing classes: TPrinter and TPrintout perform single and multipage printing. You never have to use archaic (and cranky) Windows "escapes" for printing text and graphics.
  • Module and application classes: TModule forms the basis for the TApplication class, which encapsulates the application's global functions such as startup tasks, message loops, and error handling. The TDll class provides a similar base for developing dynamic link libraries (DLL).
  • Document and view classes: Classes TDocManager, TDocument, and TView provide a document-view model that molds data for input and output purposes. In simple terms, these classes make it possible to design I/O services that function independently of the accessed information. You can associate any number of views with a specific document--representing graphics visually, for example, or as a list of coordinate values that can be edited as text.
  • Other classes: OWL is peppered with miscellaneous classes. TRect and TPoint simplify display-coordinate handling. Menu classes help you create dynamic and popup menus. The TClipboard class simplifies passing information to and from the Windows clipboard. Validation classes facilitate prompting for and verifying formatted data.
  • VBX classes: Use these classes to access Visual Basic controls, fast becoming the standard in plug-and-play object programming. VBX classes let you take advantage of "smart" controls, but still use C++ to develop your application.
  • Class library: Borland's container class library has also been completely revised, and is now entirely template based. (For an introduction to C++ templates, see my forthcoming book, Mastering Borland C++ 4.) OWL 1.0 used a similar, but less versatile, object-class library. OWL 2.0 uses only the newer template container and string classes. I discuss these classes as needed by this book's sample programs.

Header Files
Like all programming libraries in C and C++, OWL's declarations are stored in header files such as APPLICAT.H and WINDOW.H. Most headers names resemble their declared classes. The TGadget class, for example, is declared in the header file GADGET.H. (The T in the class name stands for Type.)

OWL header files are stored in the OWL subdirectory, located in the default INCLUDE path where standard headers such as STDIO.H and STDLIB.H are found, usually C:\BC4\INCLUDE. Specify this directory with a command-line -I option, or by using BC4's Options|Project... command and selecting Directories from the Topics list. Installing BC4 automatically sets the default directories for the integrated development environment and command line tools. Unless you rename your BC4 directory (never a wise move), you should be able to compile all of this book's sample programs without having to change default directory settings.

To reduce the number of individual paths the compiler searches, rather than add OWL's directory to the list of default paths, specify the OWL path name in the #include directive:

#include <owl\gadget.h>
Most OWL source files have numerous such directives for each class (or class category) the program uses. It's also possible to include all OWL classes with a single directive:
#include <owl\owlall.h>
The OWLALL.H file is the Superman of OWL headers. It makes every class available, but it also takes a long time to compile. Including OWLALL.H might save compilation time for large applications that use precompiled headers (binary representations of header files stored in .CSM compiled symbol files). Small applications and demonstrations, however, such as those in this book, should include individual headers as needed. You might also include a subset of commonly OWL used classes with the following directive, which also works well with precompiled headers:
#include <owl\owlcore.h>
To include a standard header, because those files are stored in the default path, just specify the header name prefaced with no path:
#include <stdlib.h>
To use a container class, include a header from the CLASSLIB subdirectory. This, for example, makes array containers available to the program:
#include <classlib\arrays.h>
The ANSI C++ string class, fully supported by BC4 and OWL, is part of the standard C++ library. To use this class, include its header, CSTRING.H, from the default include path:
#include <cstring.h>
The standard C null-terminated string library, containing functions such as strlen and strcpy, is still available. The string class is compatible with these time tested subroutines, and you may use the class and standard string functions in the same program. Include the standard string functions with the directive
#include <string.h>
Don't confuse CSTRING.H and STRING.H. The former (CSTRING.H) declares the ANSI C++ string class. The latter (STRING.H) declares null-terminated (also called C-style) string functions that address character data with char* pointers. You may use either or both of these headers and string types.

NOTE
In a perfect world, OWL would use only the newer ANSI C++ string class. Unfortunately, however, Windows uses null-terminated strings extensively, and for that reason, OWL typically addresses character data with common char* pointers. You may use string class objects in your own code, but be prepared to convert them to C-style strings for most Windows and OWL functions. For a string object s, use the expression s.c_str() to obtain a pointer of type const char* to the object's null-terminated string data.


Encapsulation and Inheritance
For writing object-oriented Windows programs, your primary programming tools are encapsulation and inheritance. Almost every operation you program in a Windows application will make use of these C++ object-oriented programming techniques. This isn't the time or place for a course in C++. Understanding encapsulation and inheritance, however, is critical to your success with OWL. The next two sections go over some key points as a refresher course. (C++ experts should at least skim this material.)

Encapsulation
A class encapsulates data members and member functions. They are called members to distinguish them from data and functions outside of a class. The TApplication class is a good example of encapsulation. Create an instance of the class, also called a class object, like this:

TApplication app("Window Title");
The app object encapsulates data and functions for a Windows application. To obtain a data member from the object, you normally call a member function that returns the value you want. For example, if you need the application's instance handle (an integer assigned by Windows that identifies the running application), call the GetInstance member function:
HINSTANCE hInstance = app.GetInstance();
The app object doesn't contain the GetInstance function--a common misconception. Rather, the function is defined for all objects of the class. One way to make the concept clearer is to think of GetInstance as a command or a message that is applied to the object. You are in effect saying to the app object, "do your GetInstance thing, whatever that may be." You might similarly command another object of the same class.

You can also construct a dynamic instance of a class with the C++ new operator:

TApplication* papp = new TApplication("Window Title");
If that fails, the statement throws an exception that is caught by an exception handler. In the recent past, all such statements would be followed with a test of papp's value. If papp is null, there wasn't enough memory available for new to construct the requested object, and the program would call an Error function. Because of ANSI C++ exceptions, however, it is pointless to test whether papp is null. With exceptions, the statement either succeeds or an error handler takes over. The next statement can therefore safely assume that the preceding memory allocation was successful.

Programming with exceptions takes care, and if you are new to the subject, you'll find it difficult at first. Chapter 3 explains more about exception handling in C++. For now, just be aware that critical errors are handled by this new technique.

Call class member functions for a dynamic object by using a pointer dereference operator (->):

papp->SetName("New Application Name");
By the way, for 16-bit programming, you might need to preface literal strings with LPSTR, casting the string's address to a far pointer:
papp->SetName((LPSTR)"New Application Name");
In 32-bit applications, such casts are not needed because all pointers are far by definition. There are no near (segment and offset) pointers in a 32-bit program.

Encapsulation's main benefit is the elimination of logical errors caused by passing the wrong data to a function. A typical example is a misused window handle. In conventional Windows programs, you obtain a window handle and pass it to an API function. If hWindow represents the window's handle, you might use programming such as:

RECT r;
GetClientRect(hWindow, &r);
Calling the Windows function GetClientRect fills a RECT structure r with the client area's dimensions--the space inside the window's menu, title bar, and borders. Even simple function calls like that, however, can cause a world of trouble if the program passes the wrong or an uninitialized handle argument. OWL virtually eliminates such errors because the window object encapsulates the handle. The preceding code is better written as:
TRect r;
winObject.GetClientRect(&r);
Rather than RECT, the sample uses the TRect class. Instead of calling the API GetClientRect function, the statement calls the member function of that name for winObject. There is no need to pass the window handle to the function because the handle is encapsulated inside that object.

Many functions such as GetClientRect that accept address-arguments such as &r (the address of r) are overloaded to return a value or reference of that type. The preceding sample code is even safer when written like this:

TRect r;
r = winObject.GetClientRect ( );
Or, inside another TFrameWindow (or derived class) function, you can simply call member functions directly:
TRect r = GetClientRect ( );
You may still call API functions directly, but to prevent name conflicts, you might have to preface statements with a scope resolution operator:
RECT r;
::GetClientRect(hWindow, &r);
The :: operator tells the compiler to look in the global scope for GetClientRect. (All API functions are in the global scope.) Whenever possible, this book's sample programs call encapsulated member functions. All direct calls to API functions are prefaced with a scope resolution operator even when unnecessary so you can tell what kind of function is being used in each case.

Inheritance
The second key C++ technique you will use frequently is inheritance. To add new operations and data, you derive new classes from supplied classes and insert the data and functions you need. You also override existing member functions, either to replace them completely, or to enhance what they do. For example, you can create a derived window class like this:

class TDerived: public TFrameWindow {
public:
  TMyClass(TWindow* parent, const char* title);
  // ...
};
The new class, TDerived, inherits the data and functions from TFrameWindow, known as the base class, or sometimes, the ancestor class. TDerived must define a constructor to initialize objects of the class. The constructor is typically implemented something like this:
TDerived::TDerived(TWindow* parent, const char* title)
  : TFrameWindow(parent, title),
    TWindow(parent, title)
{
  // ...
}

The first line declares the constructor's implementation. The second and third lines call the base class constructors. TFrameWindow is virtually derived from TWindow so that, in cases where a derived class might inherit TWindow from many different paths, only one TWindow object exists in the derived result. This fact means you must call TDerived's immediate base class constructor (TFrameWindow) and also the virtual base class (TWindow)


NOTE

Some classes are abstract; that is, they declare pure virtual member functions that end with = 0. You cannot create objects of an abstract class, which serves as a kind of schematic for designing other classes. You must instead derive a new class using the abstract class as a base and provide finished versions of all pure virtual member functions. The TSlider class, for example, is an abstract class. To use it, you must derive a new class from TSlider and provide implementations for each pure virtual member function.


A derived class usually overrides inherited virtual member functions. For example, all classes derived from TWindow or TFrameWindow inherit a member function declared as
virtual BOOL CanClose();
The program calls CanClose to determine whether it is safe to close a window. Normally, CanClose returns true. To replace the existing function with a new model that returns false--if a file is still open, for instance--derive a class from TFrameWindow and override the virtual function (shown in bold here):
2class TDerived: public TFrameWindow {
  BOOL fileIsOpen;  // True if file is open
public:
  TMyClass(TWindow* parent, const char* title);
  virtual BOOL CanClose();
  // ...
};
Implement the replacement CanClose function to return false if the fileIsOpen flag indicates a file is open, in which case the window must not be permitted to close (assume this flag is set by other member functions in the class):
BOOL
TDerived::CanClose()
{
  if fileIsOpen {
    // display error message
    return FALSE;
  } else
    return TRUE;
}
Users are now forced to save their work before they can close the window. Best of all, you programmed that feature without having to consider the many possible ways in which a window might be closed. You can be confident that the class always calls CanClose to determine if it is safe to close the window, so there's no need to track down all events that might cause that to happen. You simply override functions and trust classes to perform as expected.

Looking Ahead
In the next chapter, you tour BC4's integrated development environment--a necessary first step before digging more deeply into Windows programming. If you pick up new software commands easily, you can probably skim Chapter 2 and then turn to Chapter 3.

Return to C++ Bibliography

 
Site Map Search Contact