This chapter and the next five cover the basic facts that everyone needs to know about BCB. Important subjects included in this chapter are as follows:
This introduction to BCB continues in the next chapter, where I cover the new Borland additions to the C++ language and introduce several key BCB classes such as AnsiStrings and Sets. These special classes emulate features of Object Pascal. Then, in Chapter 4 you will have a look at events. Chapter 5 focuses on exceptions, Chapter 6 on using Delphi code in BCB, and Chapter 7 on graphics programming with the VCL. The latter chapter will complete my introduction to the VCL and to the syntax that is common to almost all BCB programs.
When reading this chapter, you need to remember the basic philosophy of this book. My goal here is not to plumb the depths of C++, the VCL, the Windows API, or any other hardcore technical syntax. Instead, I want to show you how to quickly build real-world applications without losing touch with the underlying Windows architecture. In these chapters there are many times when I make a pass over very complicated subjects such as C++ constructors, templates, rules of precedence, the GDI, and so on. I am, of course, aware that these are sticky subjects that take many pages to cover appropriately. However, you will not find in-depth discussions of these subjects in this book, for there are many other volumes dedicated to those topics. Furthermore, the goal of this book is to show how to use RAD tools and high-level objects to perform complicated tasks quickly, easily, and safely.
C++ is already at least 10 times faster than interpreted languages such as Visual Basic, Java, or PowerBuilder. If slowing down performance by five percent yields a 10- or 20-fold increase in reliability or ease of use, I think it is worth it to play a somewhat more cautious game. In my opinion, it is better to be nine times faster and nearly as safe as an interpreted tool than it is to be 10 times faster and 10 times more dangerous than an interpreted tool. That last 5 or 10 percent that you can eke out of the language by using every imaginable trick just isn't worth it, except in a few unusual circumstances such as compilers, operating systems, and game engines. Even in those cases, it is still probably best to use the relatively small and fast OOP-based techniques outlined in this book.
This particular chapter is unique in that it covers a number of technical subjects that are not very complex. Everything in this chapter is here because I either
Don't worry if you find most of the material in this chapter a bit too simplistic. There is some basic material that almost has to be covered in a book of this type, and once I get it out of the way, I will move on to more interesting subject matter in the next chapters.
It's time now to get started with an overview of the BCB environment, project management, the VCL, and the basic syntax used by BCB programmers. When you have completed this and the next five chapters, you should have all the knowledge you need to begin a robust, broad exploration of the all the exciting features found in BCB.
C++Builder has a project manager that you can access from the View menu. You can use this tool to add files to a project or to remove files from a project.
You can add files with the following extensions to your project, and C++Builder
will compile and/or link them automatically, as shown in
Table 2.1:
Table 2.1. Files you can add to a BCB project.
Type of File | Description |
CPP | C++ Source module. OWL and MFC are treated in the appendices. |
C | C Source module. |
PAS | Any Pascal module that will compile in Delphi 2.01. |
RC | Resource script. |
RES | Resource file. |
OBJ | Compiled C++, C, or PAS file. |
LIB | C or C++ Library file. |
DEF | Module Definition file. |
NOTE: Delphi programmers need to remember that it is not enough merely to #include a C, CPP, or PAS file in a module of an existing project. You also have to add the file itself to the project using the Project Manager or the Add to Project menu choices or speed buttons. Delphi's linker assumed that you would not reference a file from your uses clause unless you wanted it to be part of your project. C++Builder, for better or worse, makes no such assumption. You must incorporate the new file into your makefile listing, or it will not be linked into your project.
BCB projects are managed in a standard C++ makefile. The easiest way to get something into your makefile is through the project manager. Editing the makefile itself is not recommended, but C++ experts will find there are some changes to your project that can only be made by editing the makefile.
Most of the important changes which can be made to a makefile are configurable through the Options | Project or Options | Environment menu choices. The developers of BCB do not expect you to find many occasions when you will need to edit the makefile. I believe the primary reason the makefile exists is that the team grew tired of trying to manage a binary project file.
The Microsoft C++ team, on the other hand, recently grew tired of trying to manage a text-based project file! This is probably one of those cases where developers have a choice between two evils.
If you are trying to manage projects that consist of multiple executables and DLLs, you will almost certainly find the current BCB project manager inadequate. Borland C++ 5.02 will support compiling C++Builder projects. You will therefore want to consider using the advanced tools in BC 5.02 for managing huge projects.
BC5 also supports a powerful scripting language not available in BCB. As a result, I think some programmers will find a combination of BC5 and BCB to produce the ultimate C++ programming environment.
Having made my pitch to that special group of programmers who are managing massive projects, I want to end this section by stating that I find BCB includes everything I need and considerably more. The goal of this book is to talk about completing high quality projects as quickly and efficiently as possible. If that is your goal, stick with BCB and with third-party tools tailored for this environment. BCB is the ideal tool for creating C++ applications. It is state of the art and leagues ahead of any other C++ environment that is planned or available at the time of this writing.
In the last
section, in Table 2.1, I list the types of files you can include in
C++Builder. Most of these files will be generated automatically for you by the compiler,
and I list them here just so you will know why they exist and what they do. In this
section, I
will talk about all the important files that become part of your project.
Table 2.2 lists the key extensions in BCB projects.
Table 2.2. File types used in a BCB project.
File extension | Description | File type |
RC | Source for resource file. | Text |
RES | Resource file. There will usually be one RES file with the same name as your project that contains only an icon. It's best to leave this file alone. |
Binary |
CPP, C | C++ source file. | Text |
PAS | Delphi 2.01 source file. | Text |
H or HPP | C++ header file. | Text |
DSK | The location of files on the desktop. | Text |
DFM | Binary file containing form definition. Use CONVERT.EXE to translate into text. |
Binary |
MAK | The project makefile in text format. | Text |
TDS | Turbo debugger symbols. | Binary |
ILX | Incremental linker symbols. | Binary |
There are, confusingly enough, two places where you can designate the paths to
your include and lib files. One is located in the
Options | Project
| Directories/Conditionals menu choice, and the second is located in the Options
| Environment | Library section. These pages are shown below in Figures 2.1, 2.2,
and 2.3. There is also a Path for Source option in the Options |
Environment | Preferences
page.
FIGURE 2.1.
The Options menu leads to the Project dialog where you can set paths for
your project.
FIGURE 2.2. The Options menu is also the gateway to the Environment dialog where you can find the Library page.
FIGURE 2.3. The Options | Environment | Preferences page gives you a place to add the path to modules you include in your projects.
The BCB macro shown in the Path statements from Figures 2.1 through 2.3 resolves into the path that points to your current installation of BCB. This kind of information is stored in the Registry under HKEY_CURRENT_USER/Software and HKEY_LOCAL_MACHINE/Software. For instance, see the RootDir entry in HKEY_LOCAL_MACHINE/Software/C++Builder/1.0. To view the Registry, select Run from the Windows Start menu, type in the word RegEdit, and press the Enter key.
As a rule, you make changes specific to one project in the Options | Project dialog, and make global changes that you want reflected in all programs in the Options | Environment dialog. Use the Path for Source to include any directories that contain utility code that you use frequently.
As a rule, additions to these various path statements are made automatically when you add modules to your project through the Project Manager. However, there are times when I need to go in and explicitly edit one of these options.
Remember that if you are adding a component to the Component Palette, you have to add the path to that component in the Options Environment dialog or the Component Palette will not load. This addition to the Path statement will be made automatically if you add a component from inside the IDE. If you add the component from the command line by recompiling CMPLIB32.CCL, you need to update the Library path statement yourself. If you are using DLLs from inside a component, make sure the DLL is in a directory that is on your global DOS/Windows path. For instance, you might consider putting it in the Windows or Windows/System directory.
NOTE: The question of whether to call a C module used in a BCB program a unit or a module is something of an open matter in BCB. My inclination is to call it a module, because that is traditional C usage, but BCB seems to refer to them as units. To be utterly frank, this is the kind of issue that doesn't really grip me all that deeply. In particular, I'm sure you will have no trouble understanding me regardless of which term I use. As a result, you will hear me referring to C modules as either modules or units, depending more on whim than on any clearly defined practice.
Here are some tips for working inside the IDE. I'll make this section brief, because I don't want to waste time on issues like this in a book that is clearly aimed at experienced programmers. However, this is a new environment, so it might help to share a few tips.
Whatever you do, be sure that you understand that this tool is meant to be used from inside its IDE. This is not a command-line environment!
NOTE: I'm sure that most programmers who investigate the matter will see that the command-line approach makes no sense with BCB. If that sentence strikes a sour note with you, all I ask is that you don't develop contempt prior to investigation. In my opinion, this IDE has something so powerful to offer that it finally makes command-line programming obsolete. With BC5, and even with MSVC, I usually worked from the command line. I was one of the last of the hardcore C++ command-line junkies. With BCB, however, I have changed my ways. I'm totally sold on this environment, and would never consider going back to the command line except for a few rare situations.
Here are a few tips for using the visual tools. If you are new to the environment, you should boot up BCB and follow along when reading these time-saving tips.
When dropping controls on a form, do the following:
FIGURE 2.4.
Right-clicking a TTable object to bring up a list of custom options.
Here are some tips on using the IDE:
Other than the path-related issues covered in the last section, there are only a few options that you need to know about when programming BCB. The rest of the setup-related issues are handled for you automatically by the environment.
All the options you choose in the Project and Environment dialogs are written to the Registry. If you want to write custom programs that change these settings, you can learn how to proceed by reading the sections on the Registry in Chapter 13, "Flat-File, Real-World Databases."
The Options | Project menu has six pages in it:
Forms: This page is discussed in depth later in the chapter when I discuss the Project Source file for the ShapeDem program in the section called "Creating Forms." The core functionality on this page addresses the question of which unit will be the main module for your application--that is, which will come up first when you start the program. A secondary issue addressed in this page is whether a form will have memory allocated for it automatically at startup, or whether you want to create it explicitly at some point during your application's runtime. Forms listed in the left-hand list box shown on this form are created automatically; those on the right-hand list box must be created explicitly by the developer. The following code will create a form, show it to the user, and delete it:
Form2 = new TForm2(this); Form2->ShowModal(); delete Form2;This code would not work unless the header for Form2 was included in the module that wanted to call the code quoted here:
#include "unit2.h"Application: This is where you can set up the icon or help file for your project. This is an intuitive process; click Help in the dialog if you have questions.
C++: This is where you can set the Debug and Release compiler options. You can also do a small amount of fine-tuning here, but this book hardly ever steps beyond recommending that you use the Debug option in development and the Release version when you ship. I almost never have occasion to do more than choose the simple binary Debug or Release option, except for occasionally toggling the precompiled headers option.
Pascal: Here is where Pascal aficionados can fine-tune their code. I would recommend leaving all these options untouched unless you have a specific reason to change them. If you want to get involved in this page, the first level of advice is to turn Range and Stack checking on only during debug cycles, and to turn Optimizations on only when you ship.
Linker: This is where you can decide to produce a Windows or console application, an EXE, or a DLL. This is also the place where you can toggle the incremental linker on and off. In development, you probably want the incremental linker on to speed compilation; when you ship, you should test the size of your executables when it is off and when it is on, and ship the smallest version.
Directories / Conditionals: The key features of this page were covered earlier in this chapter. Note that this is also where you can define conditionals. The whole subject of unit aliases is an Object Pascal-specific issue that enables you to create an alias for the name of a unit. For instance, the 16-bit version of Delphi kept all the Windows API calls in a unit called WinProcs.pas, and all the Windows types in a unit called WinTypes.pas. When the big segment sizes of 32-bit Windows became available, the two units were consolidated into one called Windows.pas. To remove the burden of having to change the uses (#include) statements in a Pascal file, the developers enabled you to create aliases. The most common alias told the compiler to use Windows.pas whenever it saw a request to include the WinTypes or WinProcs units.
As you can see, I don't put a lot of weight on fine-tuning the settings for your project. If you flip through these pages and see the small number of options available, you can see that the developers were not very concerned about this issue either. One of the major goals of BCB is to make C++ once again a mainstream language. The programming world used to be much simpler than it is today. Now we are all expected to know about OLE, MAPI, the Internet, multimedia, or other cutting-edge technologies. I invest my time in learning these valuable new technologies, and ask little more of my compiler than that it link quickly and easily and automatically produce small, tight code.
There are six pages in the Options | Environment menu choice. I play with many of these options all the time because they do nothing more than tweak the appearance or feel of the IDE. You aren't going to accidentally mess up the link process in your program or add 500KB to the size of an executable by tweaking one of these options. Feel free to set them as you please. Following is a list of the options I often play with during development.
There are some choices that are listed in both the Environment Options pages and
the Project Options pages. If you make a change in the Project pages, you are changing
an
option for just that one project, while if you make the change in the Environment
page, you are changing things globally for the entire environment. Local options
always override global options.
Preferences: In the preferences page I
always set Show Compiler Progress to
true so that I can tell how far along I am in the compile cycle. I set AutoSave to
true for Desktop files so that the environment will remember which project I was
working on and which files I had open in the IDE.
I also frequently tweak Break On
Exception, depending on whether or not I want to catch problems in my code (turn
it on) or just test to see if exceptions are popping up as expected at runtime (turn
it off). This is also where you can turn integrated
debugging on and off and change
the path, as described above in the section on setting the project path.
Library: This is where you can set the path for include and lib files, as described previously. You can also globally decide for all projects whether or not to use the incremental linker. If you are adding components to the Component Palette, you should set Save Library source code to true so that you can build the Component Palette from the command line to save time or to repair a damaged Component library.
Editor: I discuss this page in a later section called "Feeling at Home in the IDE." It is here you can customize the behavior of the editor. All the major third-party editors (CodeWright, SlickEdit, MultiEdit) have some customizations for BCB, but none of them can get into the environment to the degree to which you, I, and they would like. Hopefully, improvements will come in this area in later releases.
Display: I discuss this page in a later section called "Feeling at Home in the IDE." It is here you can choose the keystroke emulation and font for the editor.
Colors: I discuss this page in a later section called
"Feeling at
Home in the IDE." It is here you can customize the colors of the editor. It
particular, it enables you to switch between different color schemes or customize
the colors for each element in the language, such as string,
identifiers, integers,
and so on. Like all the settings mentioned in these pages, the results of your decisions
are written to the Registry. The Address2 program from Chapter 13 shows how you could
write custom programs that tweak the Registry. For
instance, you could write a program
that automatically switched between four or five additional color schemes.
Palette: If you want to change the order in which components appear in the Component
Palette, this is the place to make your
changes. You can also reach this page by
right-clicking the Component Palette and choosing Properties. It is pretty hard to
do any serious damage to the environment using this page, but if you feel you need
help, just press the Help button on the
dialog itself.
As you can see, most of the options on these pages address only aesthetic or habit-based
preferences regarding how the IDE works. From a development point of view, the key
issues involve incremental linking, paths, and saving the
source for the Component
Palette. Make sure you understand those important issues before moving on to the
next topic of discussion.
To help make the IDE
comfortable, you might go to the Options | Environment |
Editor page, shown in Figure 2.5.
FIGURE 2.5.
The Options menu gives you access to the
Environments dialog where you
find the Editor page.
From the Editor page you can make the following changes:
Everything you can do in BCB through the visual tools you can also do in code. The visual tools are just a means of expediting the programming process. They do not supplant code, they complement it. This is what the Borland marketing department means when they talk about "two way tools." The are two different ways to approach some parts of a BCB project: in code or by using the RAD tools.
If you right-click a form, you can select the View as Text menu item to convert a form to text. To convert back, just right-click the text version of the form.
BCB also ships with a command-line utility called Convert that will convert DFM files to text, or text to DFM files. At the command line type either
convert MyForm.dfm
or
convert MyForm.txt
The first example converts a binary form to a text form with the extension TXT, and the second example reverses the process.
If you have 4DOS on your system, you can use the following command to convert all the DFM files in a branch of subdirectories from DFM to text files:
global /I convert *.dfm
This command will iterate through all the subdirectories below your current position and convert all the files in those directories to text. If you are concerned about archiving files, this is a good way to proceed. In particular, a text file is a much more robust storage medium than a binary file. If you lose one byte of a binary file, it may become worthless. Losing one byte from a text file rarely causes any serious mischief.
If you have one form and want to paste all or part of it into a second form, you can select multiple objects from the first form, choose Edit | Copy, focus the second form, and then paste the selections from the first form into it. If you want, you can have an intermediate step where you paste the items from the first form into a text editor such as Notepad, edit them, and then paste them onto a form.
NOTE: If you are a Delphi programmer and want to port a form from Delphi to BCB, you might consider using the technique outlined in the last paragraph as a way to proceed. Of course, you can compile your Delphi forms directly in BCB, but if you want to port them, just cutting and pasting to the clipboard is a good way to proceed.
Here is a what a BCB button looks like in text form:
object Button1: TButton Left = 96 Top = 16 Width = 75 Height = 25 Caption = `Button1' TabOrder = 0 end
To get this code, I Alt+Tabbed out of my word processor over to BCB, selected a button on a form, and chose Edit | Copy from the menu. I then Alt+Tabbed back to my word processor, and chose Edit | Paste. During the process the Windows button was automatically converted to text.
Here is a second version of the button code that has been slightly modified:
object MyButton: TButton Left = 1 Top = 16 Width = 75 Height = 25 Caption = `My Button' TabOrder = 0 end
As you can see, I have changed the name of the button from Button1 to MyButton, and I have changed the Caption and Left properties. Now I can select this text in my word processor, Alt+Tab over to BCB, select and form, and choose Edit | Paste to paste it back into the form. However, this time it has a new name, a new location, and new caption.
This is what is meant by a two-way tool. I can edit the form using the visual tools, or I can edit it in a word processor. It works in two different ways, depending on my current needs.
NOTE: When working with forms, remember that the currently selected component will be the target for a Paste operation. For instance, if I have selected a TButton object, and I chose Paste, BCB will attempt to make the control currently in the clipboard into a child of the button. In most cases, this is not what I want. Instead, I should first select a form or a panel, and then paste the controls onto that object. You also want to make sure the object you are pasting into is big enough to receive the control or controls you are about to dump from the Clipboard.
You have now made it through the first section of this chapter. In the next section I am going to switch my approach from a "hot tips" format to a more discursive style. If you want more information of the type you have seen so far in this chapter, you should look in the online help or pick up a book aimed at introductory BCB programming issues. Everyone has to know the kind of information I have been ladling out in the last few pages, and indeed it is vital information, but it is not the subject matter of this book. I have included this much only because I feel many of the issues addressed here are not immediately obvious when you first open BCB, and yet you absolutely have to know these facts in order to get any serious work done in the environment.
Many people are confused about Borland C++Builder. They are not used to the idea of having a RAD tool that works with C++, and they don't know quite what to make of it when they see it.
Some people think they are seeing a code generator; others think this is a visual tool meant for programmers who don't want to write code. Some people think they have found a great tool for building databases, and others a tool for prototyping applications.
There is some truth to all of these ideas, yet they all miss the mark if your aim is to find the essence of Borland C++. The core pieces of the technology are threefold:
These are things that lie at the core of C++Builder. Don't let anyone else lead you astray with tales about prototyping or about BCB being a replacement for PowerBuilder. This tool may in fact perform those roles at times, but that's not what it is all about.
NOTE: It may be that from a commercial perspective the majority of programmers will find the database support to be the most valuable aspect of this tool. Indeed, I spend a large portion of this book covering that subject. However, the emphasis on databases is market-driven, while the technological core of the product lies elsewhere.
To get at the heart of BCB, you have to understand components, you have to understand the delegation model, and you have to understand RTTI. In particular, the first two points are essential to an understanding of how this product works, while the third helps you understand why it works.
You have, no doubt, either already noticed or have heard talk about the fact that BCB has some proprietary extensions to C++. These extensions are there to support the creation and use of components as well as the associated concepts of properties and events that make components so powerful.
There was no way to create a product like BCB without extending C++ to support components, properties, and the delegation model. This is a better way to program, and there is no force in the world that can suppress it. I have no problem at all asserting that in five years time, all compilers will support these features and most programmers will use them by two years from now (1999).
Let me say it one more time, because this is so crucially important: What's key is the component model, and its reliance on properties and events. Components, properties, the delegation model. Those are the things that stand at the heart of this technology. The three tools make it easy to build databases or multimedia applications. To say that the tool is primarily about building games or databases or Web sites is putting the cart before the horse. The tool is primarily about components, properties, and the delegation model. The other strengths of the tool fall out more or less automatically once the ground work has been laid.
Now that you know something about the environment in which BCB exists, it's time to dig a little deeper and start examining the VCL object model used by BCB. The VCL (Visual Component Library) is an object-oriented library designed to ease the process of creating visual components.
NOTE: When I say that BCB uses the VCL, I mean for the phrase to be taken in at least two distinct ways. BCB uses the VCL in the sense that the physical IDE is literally built into VCL, and also in the sense that we, as BCB programmers, use the VCL as the object model of choice when creating applications. Borland is not asking you to do anything they wouldn't do. Delphi is built into the VCL. BCB is built into the VCL, and much of Latte, the new Java product, is built into the VCL. The VCL is the tool of choice for people who have a choice.
Many C++ programmers who come to C++Builder find themselves wondering why the VCL exists in the first place. What was wrong with OWL or with MFC? Why should there be yet another object framework?
The simple answer is that visual programming, RAD, needed a whole new framework with new features. RAD relied on new concepts such as event handlers, properties, property editors, components, component editors, experts, forms, and a slew of other features. The language that implemented these new syntactical elements also desperately needed improvements in the areas of streaming, string handling, object initialization, and referencing.
These features simply were not available in either the C++ or Object Pascal versions of OWL. As a result, a new framework was created that supported these features; it is called the VCL, or Visual Component Library. The name goes a long way toward explaining why OWL could never do this job correctly. This is an object-oriented library built around visual components, and visual components do not have even the most oblique reference anywhere in OWL or MFC. They are a completely new entity and required their own object-oriented framework.
Having said that, I should add that VCL is closely related to OWL, just as the current version of OWL is closely related to the 16-bit version of OWL from which it grew. If you know OWL, you will find much in VCL that is familiar. Indeed, even MFC is a good background for understanding VCL. However, this is a fundamentally different kind of beast, one that is built around a new concept called a visual component.
NOTE: I am aware, of course, that ActiveX is another specification for building visual components. The difference between ActiveX and the VCL is that the VCL is specifically designed to be used with advanced programming languages such as C++ or Object Pascal. ActiveX was designed to be used in a broader context featuring a wide variety of languages and operating systems. ActiveX is more powerful than VCL, but it is also much bigger, much more complex, and slower.
To conclude this brief introduction to the long discussion of the VCL found in this chapter, I feel it is important to explicitly state that I am aware that many hardcore C++ programmers will not automatically greet the VCL and its occasional bits of nonstandard C++ syntax with open arms. When writing this chapter, I am conscious of the need both to explain the VCL and also explain exactly why the VCL exists.
I want to make it absolutely clear that I am one hundred and ten percent committed to the VCL, and have absolutely no doubt that it represents the correct model for programming at this point in the ongoing development of programming tools and languages. I will occasionally make statements that explicitly justify some part of the VCL in the face of possible criticisms. These statements do not in any sense represent doubts in my own mind about the VCL. I prefer the VCL to OWL or MFC, and I am absolutely certain that all of the extensions to the C++ language that it introduces are necessary and represent significant advances for the language.
I am aware that some readers have large quantities of legacy OWL and MFC code that they want to preserve. I do not take those needs lightly and feel it is my obligation to state explicitly why changes have been made to the C++ object model.
BCB enables you to use MFC and OWL code, but the only reason this feature exists is to support legacy code. I personally do not think either MFC or OWL is the best choice any longer for large programming projects. The introduction of the component, property, delegation model offers such vast improvements in our ability to write code, that I don't think it's advisable to use OWL or MFC simply because of the poignant weight of all that legacy code. This is not a criticism of these two excellent object frameworks. The point is simply that they do not support components, properties, or events, and without that support they don't meet my current needs. I simply cannot imagine that anyone else who works with BCB for any significant length of time could possibly come to any other conclusion.
I am aware, however, that this is a controversial subject. Over the last four years I have been using the VCL almost every day. Most of that experience is on the Delphi side, but I have also been using BCB every day for about five months at the time of this writing. During the last four years, I have had many occasions to also use MFC and OWL. After my first six months with the VCL, there was never one single time that I went back to MFC or OWL without the feeling that I was using a much loved but outdated system that lacked fundamental features that have become a necessary part of what I believe to be the best contemporary programming model. This doesn't mean that I won't use OWL or MFC if it has an existing object in it that I find appealing, but only that I prefer to use the VCL if that is an option. Indeed, in most cases, the VCL provides for all my needs.
If a language or programming model does not support properties, components, and delegation, I personally find it lacking, no matter how many other fine features it may present to the user. VCL is the right way to go, and it would be absurd to read my occasional arguments on its behalf as representative of doubts in my own mind about this programming model.
In a sense, my job would be easier if the VCL were harder to use. It was very frustrating for me to realize that I had to start leaving behind all that detailed knowledge about OWL in order to use a system that was so much simpler to use. Wasn't there something that the VCL was missing? Didn't there have to be a catch? How could the solution to so many long-term problems turn out to be so simple?
Well, I believe that there isn't a catch, and that the VCL does everything that OWL or MFC did, but does it better. Of course, OWL and VCL have tremendous depth in terms of the number of objects in the existing hierarchy, which is one reason why BCB supports them. You will find, however, the VCL has great depth itself, and that it supports all the features, if not all the objects, found in MFC and OWL. In other words, if you find that a particular object you used in OWL is not part of VCL, you could either use the existing object or create a new one that does the same thing in the VCL. The VCL supports all the features of OWL, but may not at this time have duplicates of all its objects. Furthermore, there is tremendous existing third-party support for the VCL that helps fill in some of the gaps.
For good measure, the VCL adds some new tricks regarding components, properties, and events that can't be found in either OWL or MFC. The kicker, of course, is that the VCL produces code that is at least as fast and small as OWL or MFC, but it is literally about ten times easier to use.
Let me give it to you straight. Back in the old days I loved OWL and thought it was the greatest thing I had ever seen. I used it all the time and was a fanatical adherent of that programming model. I now use VCL for everything, and go back to OWL or MFC only when I absolutely must. To my eyes, the support for components, properties, and events makes VCL clearly better than OWL or MFC in the same sense that OWL was clearly better than structured programming. If you love OWL, it is sad to hear this kind of thing, but components, properties, and events simply bring something new to the table that OWL and MFC simply can't emulate.
It is hard to accept the rate at which programming languages are evolving at this time. The burden this places on programmers is immense. The size of this burden is one of the key reasons I advocate, again and again, doing things the simplest, easiest, safest way. You have enough to worry about without trying to figure out the last picayune details of constantly changing standards!
The learning curve associated with the onrush of progress, our attachment to legacy code, a sentimental attachment to the ANSI committee (of all things!)--none of these represent a reason to stick with an outdated technology when a clear advancement in programming tools appears. I obviously have a involvement with Object Pascal. But I am also a long-term (10 years) C++ programmer, and I know about the deep attachment we all feel to the rules of this language, and I know what a huge improvement OWL and MFC represent over structured programming. However, things have changed again, and personally, I love it! VCL is wonderful. I never waste two seconds thinking about going back to the old way of doing things.
NOTE: One final note on this subject ought to go out to readers of my Teach Yourself Windows and Teach Yourself Windows 95 Programming books. Those books talk about straight C Windows API programming (a la Petzold) without the aid of any object framework. I do not believe there is anything in this book that makes the material in those books obsolete.
To be a good Windows programmer you still have to know the Windows API, and you still have to know about message loops and window procedures. Indeed, in Delphi 2 Unleashed, I went on for several hundred pages about material of that sort. I do not include any of that kind of material in this book because there are many C++ programming books that cover that material. (There were not any, as far as I knew, Pascal books that covered that material, which is why I included it in Delphi 2 Unleashed.)
Needless to say, I think you ought to use the VCL to write contemporary Windows programs. However, if you also know the Windows API, you can become a great VCL programmer. If you don't know the Windows API, you will always be at a loss when using some features of the VCL, at least until such time as Windows becomes a true object-oriented operating system. Do you have to know OWL or MFC to become a great VCL programmer? No.
Do you have to know the Windows API in order become a great VCL programmer? Absolutely! The Windows API material found in my Teach Yourself... books has a very long life from a technical, if not a commercial, point of view. Assuming that Java does not take over the world, a knowledge of the Windows API is always invaluable to a contemporary programmer.
Remember, however, that even the glorious VCL has a shadow over it, in the form of Java. I never get too bogged down in the details of the VCL or any other framework, because I never know when this rapidly changing programming world is going to enter another period of mind-numbing change in which the slow ones get left behind!
The Windows API is the only really complex programming paradigm that is worth learning in depth, because all the others are likely to change over time. The fact that the VCL is easy to use is not just a nice feature, it's a necessity in this contemporary programming world where the only constant is change. I should perhaps add that the reason you use the VCL instead of the raw Windows API is because you get your work done in about one-tenth the time. If you use OWL or MFC, your code will be much bigger than if you use raw Windows API code, but you will also have a better chance of getting your work done on time. If you want another four- or five-fold increase in productivity, use the VCL.
Now that you have heard an advertisement for the VCL, it might help to provide a few more basic examples illustrating some of the virtues of this programming system. These are very simple examples that are a bit atypical of the type of code you will see in this book, or even in the latter portions of this chapter. I feel, however, that these basic examples are useful when illustrating some of the key features of RAD programming with the VCL. Their presence ensures that everyone understands the benefits of visual programming with the VCL.
I will, however, use these basic examples to explore some fairly complex aspects of the VCL, and especially of the code that executes just before and after the main form of your application is made visible. In particular, I will look at the code in the project source file for a typical BCB application that uses the VCL.
The first program I want to discuss uses a standard Delphi component called TShape. If you open up BCB and drop the TShape component on a form, you will see that it draws a simple white rectangle on the screen.
Of course, the TShape object can perform more than this one simple trick. For instance, if you pull down the list associated with the Shape property in the Object Inspector, you see that you can easily work with ellipses, circles, squares, and other assorted shapes. Furthermore, if you expand the Brush property, you can change the shape's color. The pen property enables you to change the width and color of the outline of a TShape object.
NOTE: Don't forget that you can expand properties that have a plus sign (+) next to them by double-clicking the property's name. A Color property always has a dialog associated with it. To bring up the dialog, double-click the area to the right of the Color property. This area is called the property editor. (Later in the book I will show how to create your own property editors and how to use existing property editors.) Select a color from the dialog, click the OK button, and the color you chose automatically takes effect.
As just described, it's trivial to change the major characteristics of a TShape object at design time. However, I spoke earlier about BCB supporting two-way tools. Anything that you do with the visual tools you can also do in code. It is, of course, a little more work to make the same changes at runtime that you made at design time, but the basic principles are still simple. The SHAPEDEM and SHAPEDEM2 programs on the CD that accompanies this book show you how to proceed.
NOTE: I try to avoid it, but you might find a few places in the sample programs where I hard-code in the path to a file. You will probably have to edit these paths to get the program to run on your system.
I go to a great deal of effort to ensure that the code that accompanies this book works correctly. If you are having trouble running any particular program, check the readme files found on the CD that accompanies this book. If you still can't get the program running, go to my Web site and see if there is an update, hint, or bug report available. My Web site is users.aol.com/charliecal.
At its core, the SHAPEDEM program consists of nothing more than a TShape object placed on a form, along with two scroll bars and a menu. What's interesting about the program is the ease with which you can change the size, color, and shape of the TShape object at runtime.
You can find the code for the program in Listings 2.1 through
2.3. Remember that
if you want to view the source for the project file in your application, you can
select the View | Project Source menu item.
Listing 2.1. The code for
SHAPEDEM.CPP.
#include <vcl.h> #pragma hdrstop USEFORM("Main.cpp", Form1); USERES("ShapeDem.res"); WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); return 0; }
Listing 2.2. The header for the main unit in SHAPEDEM.
#ifndef MainH #define MainH #include <Classes.hpp> #include <Controls.hpp> #include <StdCtrls.hpp> #include <Forms.hpp> #include <ExtCtrls.hpp> #include <Dialogs.hpp> #include <Menus.hpp> class TForm1 : public TForm { __published: TShape *Shape1; TScrollBar *ScrollBar1; TScrollBar *ScrollBar2; TColorDialog *ColorDialog1; TMainMenu *MainMenu1; TMenuItem *Shapes1; TMenuItem *ShapeColor1; TMenuItem *FormColor1; TMenuItem *Shapes2; TMenuItem *Rectangle1; TMenuItem *Square1; TMenuItem *RoundRect1; TMenuItem *RoundSquare1; TMenuItem *Ellipes1; TMenuItem *Circle1; void __fastcall ShapeColor1Click(TObject *Sender); void __fastcall FormColor1Click(TObject *Sender); void __fastcall Rectangle1Click(TObject *Sender); void __fastcall ScrollBar1Change(TObject *Sender); void __fastcall ScrollBar2Change(TObject *Sender); void __fastcall FormResize(TObject *Sender); private: public: virtual __fastcall TForm1(TComponent* Owner); }; extern TForm1 *Form1; #endif
Listing 2.3. The code for the main unit in SHAPEDEM.
#include <vcl.h> #pragma hdrstop #include "Main.h" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { Shape1->Left = 0; Shape1->Top = 0; } void __fastcall TForm1::ShapeColor1Click(TObject *Sender) { if (ColorDialog1->Execute()) Shape1->Brush->Color = ColorDialog1->Color; } void __fastcall TForm1::FormColor1Click(TObject *Sender) { if (ColorDialog1->Execute()) Form1->Color = ColorDialog1->Color; } void __fastcall TForm1::Rectangle1Click(TObject *Sender) { Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag); } void __fastcall TForm1::ScrollBar1Change(TObject *Sender) { Shape1->Width = ScrollBar1->Position; } void __fastcall TForm1::ScrollBar2Change(TObject *Sender) { Shape1->Height = ScrollBar2->Position; } void __fastcall TForm1::FormResize(TObject *Sender) { ScrollBar1->Max = ClientWidth - (ScrollBar2->Width + 1); ScrollBar2->Max = ClientHeight - (ScrollBar1->Height + 1); ScrollBar1->Left = 0; ScrollBar2->Top = 0; ScrollBar2->Left = ClientWidth - ScrollBar2->Width; ScrollBar2->Height = ClientHeight; ScrollBar1->Top = ClientHeight - ScrollBar1->Height; ScrollBar1->Width = ClientWidth - ScrollBar2->Width; }
In the next few paragraphs, you'll hear a discussion of how to change the color of the form, the shape shown on the form, and the size and shape of the object itself.
When you run the SHAPEDEM program, it looks like the screen shot shown in Figure 2.6. Use the program's scrollbars to change the size of the figure in the middle of the screen. Use the menu to select a new shape for the object and to bring up a dialog that enables you to change the color of either the form or the shape.
FIGURE 2.6. You can use the scrollbars and buttons to change the appearance of the SHAPEDEM program's form.
Before getting into any details about how the ShapeDem program works, it might be helpful to take one moment to look at the project source. You can access the project source from the View menu.
This is the place in the book I have chosen to give you a close look at how Borland blended its Pascal and C++ technology into one product. I will not show much Object Pascal code in this book, but you will see quite a bit in the next few pages.
At the top of the project source you see code that brings in the VCL:
#include <vcl/vcl.h> #pragma hdrstop
In many programs, you will not have to add any other include statements to your program other than this one, except when you want to include other units from your current project, and even that can be automated through the File Include Unit Hdr menu option. That's not a hard and fast statement; I only mean to imply that you don't tend to spend a lot of time adding include statements unless you want to access obscure features of the Windows API. Many other include statements will be added to your header files, however, when you drop components down on a form.
The pragma statement shown here tells the compiler that you only want to have the VCL in your precompiled headers. Any additional files should be recompiled each time you do a build or a make. This is done for the sake of efficiency, because your own header files are likely to change on a regular basis.
The next few lines tell the project manager to bring in forms or resources:
USEFORM("Main.cpp", Form1); USERES("ShapeDem.res");
You would not normally add this kind of code yourself, but would ask the visual tools to insert it for you. In this case, the tool you would use is the Project Manager from the View menu. However, you don't have to use the Project Manager; you can make this changes manually if you wish, and the Project Manager will pick up on your work.
Here is the WinMain block for your program:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run(); return 0; }
As you can see, BCB assumes you won't be using any of the parameters passed to it. In particular, the VCL provides a global variable called HInstance, which provides the HInstance for your application. The HPrevInstance variable is never used in Win32 programming. Parameters passed to your program are available in the form of a VCL function called ParamStr, and a variable called ParamCount. To get at the name and path of your executable, access ParamStr(0), to get at the first parameter passed to it, access ParamStr(1), and so on. ParamCount contains the number of parameters passed to your executable. For instance, the following code pops up a message box showing the name and path of your executable:
void __fastcall TForm1::Open1Click(TObject *Sender) { ShowMessage(ParamStr(0)); }
NOTE: Wary C++ programmers may be concerned about the fact that the VCL has gotten at the HInstance and program parameters before they reached WinMain. Don't worry, there is no huge subsystem underlying your entire program! However, a few things do happen before WinMain is called, just as a few things happen in all C++ compilers before WinMain is called.
This is a case where it might help to talk about how Object Pascal handles program startup. Object Pascal simply did not support WinMain on the grounds that it was faster and more convenient to perform processing of HInstance and the program parameters without setting up a stack frame for a function call. The Object Pascal system is faster and more efficient because you don't have to push things on the stack before calling a function named WinMain. On the other hand, the overhead of calling WinMain with four parameters is hardly significant in the face of the time it takes to load a contemporary Windows program into memory. Of course, BCB provides a WinMain for you so you will be able to compile standard C++ programs without modification.
The key point to notice here is that the VCL provides extra support for you without affecting the performance of your program and without changing the way that C++, or standard Windows programs, operate. This is the right way to handle the interface between C++ and Object Pascal. No changes to C++, no impact on performance, no changes to the standard Windows programming model, and yet additional features are provided for the programmer in terms of ParamStr and the global HInstance.
After getting to WinMain, the program uses the pre-initialized Application object to get your program up and running:
Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->Run();
I will discuss each of these calls in their own sections of this chapter. Brace yourself, because I am going to step you through the Object Pascal code that underlies each of these calls.
The first call in WinMain performs some initialization:
Application->Initialize();
The two most important tasks performed by this code involve OLE and database code. If there is database programming in your application, the database code starts an object called TSession. If there is OLE code in your program, the program may call a method that updates the Registry automatically so that your program is a properly registered automation server. If the code uses neither technology, nothing happens in the call to initialize.
Here is the code for the Initialize method of TApplication as it appears in Forms.pas:
procedure TApplication.Initialize; begin if InitProc <> nil then TProcedure(InitProc); end;
If you want to step through this code, simply copy Forms.pas, Controls.pas, Classes.pas, and VCL.INC from the BCB\SOURCE\VCL subdirectory into your project directory and add Forms.pas, Controls.pas, and Classes.pas to your C++ project. To add the files, bring up the Project Manager from the View menu, click the plus button, and use the drop-down list from the Files of Type section to browse for files with a .pas extension. Next, select Forms.pas, Classes.pas, and Controls.pas, and close the Project Manager. This technique is probably preferable to bringing in the source to the whole VCL by adding the CBuilder\SOURCE\VCL to the include or library path for your project.
When you step through this Object Pascal code in the BCB integrated debugger, you will find that InitProc is never called, because it is set to NULL unless you bring in database code or OLE automation code. Needless, to say, it takes one line of assembly code to test for NULL, so there is no significant overhead involved here other than the call to the Initialize method itself.
Here is the code at the bottom of the OLEAuto unit that would initialize InitProc if you used OLEAutomation in your program:
initialization begin OleInitialize(nil); VarDispProc := @VarDispInvoke; Automation := TAutomation.Create; SaveInitProc := InitProc; InitProc := @InitAutomation; end; finalization begin Automation.Free; OleUninitialize; end; end.
This call starts by initializing OLE and then setting up a dispatch point for use when making calls into IDispatch. Next, it allocates memory for the Automation object, calls its constructor, and finally points InitProc at the proper method after saving the previous value of the function pointer. Of course, none of this code will be called unless you are using OLE Automation in your program through the routines found in the OleAuto unit.
The second call in WinMain creates the MainForm for your application:
Application->CreateForm(__classid(TForm1), &Form1);
You already have the Forms unit linked into the project, so you can step into this code without any further work. However, if you want to get into more detail here, you can also add Classes.pas and Controls.pas to your project. However, there are very few users of the VCL who really need to know what happens in either controls or classes.
Here is the call to CreateForm:
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference); var Instance: TComponent; begin Instance := TComponent(InstanceClass.NewInstance); TComponent(Reference) := Instance; try Instance.Create(Self); except TComponent(Reference) := nil; raise; end; if (FMainForm = nil) and (Instance is TForm) then begin TForm(Instance).HandleNeeded; FMainForm := TForm(Instance); end; end;
The vast majority of this code does nothing but initialize variables or check for errors.
The base VCL object is called TObject. All VCL objects descend from TObject by definition. It is impossible to create a VCL object that does not descend from TObject, because any object that does not descend from TObject is not part of the VCL. The call to TObject.NewInstance does nothing more than allocate memory for an object and return a pointer to it, as you can see from viewing this call in System.pas:
class function TObject.NewInstance:TObject; asm PUSH EDI PUSH EAX MOV EAX,[EAX].vtInstanceSize CALL _GetMem MOV EDI,EAX MOV EDX,EAX POP EAX STOSD { Set VMT pointer } MOV ECX,[EAX].vtInstanceSize{ Clear object } XOR EAX,EAX PUSH ECX SHR ECX,2 DEC ECX REP STOSD POP ECX AND ECX,3 REP STOSB MOV EAX,EDX POP EDI end;
Needless to say, this is probably the only place you will ever see anyone allocate memory for an object by calling NewInstance. As a rule, the constructor for an object will call NewInstance automatically. Here is the standard code for creating an object:
TMyObject *MyObject = new TMyObject();
This will call NewInstance for you automatically, and it would be madness to proceed in any other fashion.
NOTE: TObject.NewInstance is what is called in Object Pascal a class method and what C++ implements as a static method. Class methods and static methods can be called without an object instance; that is, they can be called before you allocate memory for an object or call its constructor. Class methods could have been implemented as functions that are associated with a class, and indeed, there is no significant difference between a function associated with a class and a class method. However, it is syntactically useful from the user's point of view to include them in a class declaration, because it makes the association between the class and the method obvious. In other words, class methods are aesthetically pleasing, and they help you create logical, easy-to-read code. Besides NewInstance, the following methods of TObject are all class methods: ClassName, ClassNameIs, ClassParent, ClassInfo, InstanceSize, InheritsFrom, MethodAddress, and MethodName.
These methods are segregated out of the BCB declaration for TObject and declared as a class with the somewhat intriguing name TMetaClass. This metaclass is a trick that allows C++ to get along with the VCL and its associated RTTI. TMetaClass plays no significant role in standard BCB programming and is designed primarily for the use of the compiler team itself. You may have occasion to use this metaclass when working with RTTI, but as a rule, it lies outside the realm of standard BCB programming. If you have the time and inclination to pursue this matter, the whole class is declared and implemented in SYSDEFS.H.
For instance, the ClassType method of TObject returns a variable of type TClass, and TClass is defined as a pointer to TMetaClass. You can use variables of this type to perform the same kinds of comparisons you would perform on standard C++ classes with typeid.
The most important call in TApplication CreateForm is to Instance.Create(Self). It is the call that ends up calling the constructor for the main form, as well as calling the Windows API CreateWindowEx function. If you step into this code with the debugger, you will find that it ends up stepping right into the constructor for your main form:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { }
The last few lines of code in the CreateForm method set the variable FMainForm to the current form if it has not already been assigned.
The main form for your application will be the one that appears first when someone launches your executable. As you can see from examining the code in CreateForm, the first object passed to CreateForm will be the main form for that application:
if (FMainForm = nil) and (Instance is TForm) then begin TForm(Instance).HandleNeeded; FMainForm := TForm(Instance); end;
Each project that you create can have zero, one, or more forms. You can add forms to a project from the File menu or from the File | New menu choice. If you add three forms to a project, this is what the project source looks like:
Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->CreateForm(__classid(TForm2), &Form2); Application->CreateForm(__classid(TForm3), &Form3); Application->Run();
In this case, Form1 will be the main form for the application. If you change the order of these statements, another form could become the main form:
Application->Initialize(); Application->CreateForm(__classid(TForm3), &Form3); Application->CreateForm(__classid(TForm1), &Form1); Application->CreateForm(__classid(TForm2), &Form2); Application->Run();
In the preceding code, Form3 is now the main form for the application.
Normally, I do not edit the project source for my application directly. Instead, I go to the Options | Project menu item and use the Forms page from the Project Options dialog to make these kinds of changes. Once again, this is a two-way tool, and you can do things visually via the Project Options dialog or you can do things manually in code. It's up to you to make the decision.
The last call in WinMain is to the Run method:
Application->Run();
TApplication.Run does a few initialization chores, and then calls a method called HandleMessage that in turn calls ProcessMessage:
function TApplication.ProcessMessage: Boolean; var Handled: Boolean; Msg: TMsg; begin Result := False; if PeekMessage(Msg, 0, 0, 0, PM_REMOVE) then begin Result := True; if Msg.Message <> WM_QUIT then begin Handled := False; if Assigned(FOnMessage) then FOnMessage(Msg, Handled); if not IsHintMsg(Msg) and not Handled and not IsMDIMsg(Msg) and not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then begin TranslateMessage(Msg); DispatchMessage(Msg); end; end else FTerminate := True; end; end;
This is the standard message loop that lies at the bottom of all Windows applications. As you can see, it calls TranslateMessage and DispatchMessage just like every other message loop in every other Windows application.
The HandleMessage routine that calls ProcessMessage also ends up calling a method named Idle. If you hook the OnIdle handler to TApplication, you can get a chance to perform background tasks while your application is running. It is generally not a good idea to respond to OnIdle events unless you are absolutely positive you know what you are doing. On the other hand, I would not suggest starting up a second PeekMessage loop in your application, so if you have to do background processing, I would indeed do it in response to OnIdle.
You now know what happens when a VCL application is first launched. I've shown it to you in such depth for two reasons:
It's time now to come back to the original purpose of this section of the chapter, which is to examine the ShapeDem program. To create the program yourself, start by dropping down a TMainMenu and a TColorDialog. The TColorDialog is found on the Dialogs page of the Component Palette, while the TMenu is on the Standards page. Now create a menu heading called Options and beneath it two menu items with captions that read Shape Color and Form Color.
NOTE: I briefly introduced using the Menu Designer in Chapter 1. Remember that you can get help on most (in a perfect world it would be all) BCB components, code elements, or tools by selecting the item in question and pressing F1.
After closing the menu designer, double-click the menu item you made called Form Color to create a method in the editor that looks like this:
void __fastcall TForm1::FormColor1Click(TObject *Sender) { if (ColorDialog1->Execute()) Form1->Color = ColorDialog1->Color; }
When you run the program, the code shown here pops up the ColorDialog,
as shown in Figure 2.7.
FIGURE 2.7.
The Color Dialog gives the user an easy way to select a valid color at
runtime.
If the user clicks the OK button in the form, the following line of code is executed:
Form1->Color = ColorDialog1->Color;
This line of code sets the Color property for Form1 to the color that was selected by the user inside of ColorDialog1.
The technique just shown can be used to change the color of the TShape object. All you need to do is drop down a TShape object from the additional page, and then associate some code with the Shape Color menu item:
void __fastcall TForm1::ShapeColor1Click(TObject *Sender) { if (ColorDialog1->Execute()) Shape1->Brush->Color = ColorDialog1->Color; }
What could be simpler?
You should now run the SHAPEDEM program so that you can see how easy it is to change the color of the elements on the form. Of course, you don't have to give the user the exclusive right to control all the elements of your program. Sometimes you can take the initiative. For instance, you could change the color of your form or of an element on your form in order to focus the user's attention on a particular part of the screen.
Notice that the code written here is all but self-documenting. Anyone with even the slightest acquaintance with programming can just glance at this procedure and determine what it does. Assuming you have more than a passing acquaintance with programming, here is how to translate the code into English: "If the user clicks on a visual element on Form1 that is called ShapeColor1, that visual element will delegate an activity to Form1. That activity consists of popping up a color dialog and asking the user to make a selection. If the user chooses the OK button, the color of Shape1 is set to the color selected in the TColorDialog object."
What do I mean when I say that the ShapeColor menu item "delegates" an activity to Form1? Well, this means that the ShapeColor TMenuItem object does not itself handle being clicked, but instead allows the form to decide what happens when someone clicks it. It delegates the job to the main form.
The delegation model works through events, which are listed in the Object Inspector on the page sitting next to the properties for an object. Some people call events closures, though I regard that as a very technical term with certain platform-specific ramifications that I have never fully explored. As a result, I will usually play it safe and call events nothing more than events and leave it at that. The delegation model is implemented through events, and events are explored in depth in Chapter 3, "Events," and Chapter 4, "Exceptions."
In the beginning, it is probably simplest to think of events as being similar to standard Windows messages, such as WM_CREATE, WM_COMMAND, or WM_VSCROLL. Indeed, handling standard Windows messages is one of the functions of events. However, you will see that events can also delegate to another control tasks that you usually could not handle unless you subclassed a component such as an edit control, or else descended from an object that wrapped a control, as you would in OWL or MFC.
Perhaps an acceptable first crack at defining events would be to say that: "Events allow you to delegate tasks from one component to another component so that you do not have to subclass the first component, nor respond inside a window procedure to messages that the first control generates. In particular, events are usually delegated by the controls on a form to the form itself."
The advantages of the delegation model are threefold:
It makes sense that it should not be very difficult to change the color of an object found on one of the forms you create. But using scrollbars to change its shape at least appears to be a more difficult task. In fact, experienced Windows programmers know that using scrollbars in a Windows program can be a fairly difficult task, one that requires you to trap and parse a number of messages in a complex switch statement. BCB, however, reduces the entire task of responding to a scrollbar to a single line of code.
To get started, first drop two scrollbars on the screen and set the Kind property of one of them to sbHorizontal, and the Kind property of the other to sbVertical. Now, turn to the events page of the Object Inspector and create a method for the OnChange event of each scrollbar. Fill in the methods with two lines of code so that they look like this:
void __fastcall TForm1::ScrollBar1Change(TObject *Sender) { Shape1->Width = ScrollBar1->Position; } void __fastcall TForm1::ScrollBar2Change(TObject *Sender) { Shape1->Height = ScrollBar2->Position; }
The code shown here sets the Width and Height of the TShape object to the current position of the thumb on the scrollbar. Clearly it is extremely easy to write BCB code that performs a task, which would be relatively complex to execute if you had to work directly with the Windows API. The VCL always makes you feel as though you are working directly with an object, and tries to hide the complexities that are introduced by the Windows API or by standard object frameworks.
NOTE: Note in particular that you don't have to first set a property of a control and then ask it to redraw itself! This magic is the result of the set method associated with the Height property of Shape1. I will explore the Set and Get methods of properties later in this chapter.
You use the program's menu to change the shape of the TShape component. In particular, a portion of the main menu for the program should have the following items on it:
Rectangle1: TMenuItem Tag = 0 Caption = `Rectangle' Square1: TMenuItem Tag = 1 Caption = `Square' RoundRect1: TMenuItem Tag = 2 Caption = `RoundRect' RoundSquare1: TMenuItem Tag = 3 Caption = `RoundSquare' Ellipes1: TMenuItem Tag = 4 Caption = `Ellipse' Circle1: TMenuItem Tag = 5 Caption = `Circle'
The Tag field is a special field associated with all components that can be set to an integer value. The VCL has no use for this field; it is designed explicitly for you to use as you wish, for whatever purpose you want.
All six menu items should be associated with the same event handler through their OnClick method. You can create the original handler by open up the Menu Designer, turning to the Events page in the Object Inspector, and double-clicking the OnChange event. If you then select the OnChange event for the other menu items, you will see that it opens up into a combo box from which you can select all the compatible events. For each of the other five menu items, select the event you created for the first menu item. The event itself should look like this:
void __fastcall TForm1::Rectangle1Click(TObject *Sender) { Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag); }
I will explain what this code means over the course of the next few paragraphs.
NOTE: Just now I said that the events that appear in the drop-down combo for the Events page of the Object Inspector must be compatible with a particular event. This means that they must have the same signature, which is yet another term that needs explanation! The signature of an event is found in the header for the method handler associated with a particular event, and in particular it is represented by the parameters passed to the handler. For instance, the signature for the Rectangle1Click method is represented by the fact that it is a method that returns nothing and which accepts a single parameter of type TObject *. Methods that look like this are of type TNotifyEvent. Here is the declaration for TNotifyEvent as it appears in CLASSES.HPP:typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);
When you see this signature for an event, you can assume that Sender contains an instance of the object that sent the event. For instance, in the case currently under consideration, it is a TMenuItem that sent the event, and the specific TMenuItem that sent the message is passed in the Sender parameter.
The items in the menu designate each of the possible shapes the TShape component can assume. You can find these words listed in the online help under the listing for TShapeType. Or, if you want to go back to the original source code, you find the following enumerated type:
enum TShapeType { stRectangle, stSquare, stRoundRect, stRoundSquare, stEllipse, stCircle }
NOTE: You can also access the names of the members of enumerated type by using the GetEnumName and GetEnumValue functions from the TypInfo unit, which is pronounced "tip-info."
In this particular case, you need to write only one line of code in response to the event that occurs when a user clicks a TMenuItem:
Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag);
This line of code sets the Shape1->Shape property to the shape that the user has selected in the combo box. The code works because of the correspondence between the ordinal members of an enumerated type and the numerical value you assigned to the tag property of a menu. In other words, the first element in an enumerated type has the value zero, as does the first tag property.
You know, and I know, that the Sender variable contains an instance of the TMenuItem item class. However, the wonders of polymorphism allow BCB to declare this variable to be of type TObject. That way, a TNotifyEvent can accept objects of any type. (Polymorphism will be addressed later in the book in Chapter 21, "Polymorphism.")
The programmer's knowledge of the Sender parameter needs to be shared with the compiler. In other words, you and I need to have some way of telling the compiler that the object is not really of type TObject, but that it is a just a polymorphic disguise for a TMenuItem. This is an important step because the TObject class does not have a Tag field, and the TMenuItem class does have a Tag field. In this case, it is absolutely necessary to get at the Tag field because it contains the code information about the shape the user wants to select.
To cast a TObject as a TMenuItem you can use the dynamic_cast syntax. This syntax can be used as part of RTTI to test whether a component is of a particular type, or it can be used to actually make the cast. In this case I am daring and make the cast without bother to test it first. This is safe in this case because I know the only object that can be passed here is a TMenuItem. However, if I wanted to be extra careful, I could write the following:
if (dynamic_cast<TMenuItem*>(Sender)) Shape1->Shape = TShapeType(dynamic_cast<TMenuItem*>(Sender)->Tag);
This would ensure that no exceptions were raised if the cast should happen to fail, which it won't in this particular case.
The final part of the ShapeDem program that is worthy of discussion involves making sure that the scrollbars cling to the bottom and right edges of the control. To do this, you need to respond to WM_SIZE messages, which come to BCB programmers under the friendly guise of an OnResize event:
void __fastcall TForm1::FormResize(TObject *Sender) { ScrollBar1->Max = ClientWidth - (ScrollBar2->Width + 1); ScrollBar2->Max = ClientHeight - (ScrollBar1->Height + 1); ScrollBar1->Left = 0; ScrollBar2->Top = 0; ScrollBar2->Left = ClientWidth - ScrollBar2->Width; ScrollBar2->Height = ClientHeight; ScrollBar1->Top = ClientHeight - ScrollBar1->Height; ScrollBar1->Width = ClientWidth - ScrollBar2->Width; }
This code uses third grade math (the only kind I ever understood!) to ensure that the scrollbars are allowed as tall or as wide as the form, no matter how it is stretched or pulled by the user. Furthermore, it ensures that the Min and Max property for the control ranges from zero to the exact size of the current client window. This ensures that you can use the OnChange event for the scrollbar to make the TShape object as large as the client window, but no larger.
Notice also that the TForm object has properties called ClientWidth and ClientHeight. These properties calculated the client size of the current window; that is, the size of the window minus the menu, caption, and frame. If this was done for you, you would have to write code that looked something like this:
int Menu, Caption, Frame; Caption = GetSystemMetrics(SM_CYCAPTION); Frame = GetSystemMetrics(SM_CXFRAME) * 2; Menu = GetSystemMetrics(SM_CYMENU); ScrollBar1->Left = 0; ScrollBar2->Top = 0; ScrollBar1->Max = Width; ScrollBar2->Max = Height; ScrollBar2->Left = Width - Frame - ScrollBar2->Width; ScrollBar2->Height = Height - Frame - Caption- Menu; ScrollBar1->Top = Height - ScrollBar2->Width - Frame - Caption - Menu; ScrollBar1->Width = Width - ScrollBar2->Width - Frame;
The point here is that without having to know about GetSystemMetrics and all its associated constants, you can still write code that calculates the size of the TShape object down to the last pixel.
If you look on the disk, you will find a second copy of the ShapeDem program, called ShapeDem2, that uses a combo box as well as a menu to let the user select the current shape of the object. For instance, you can drop down the combo box and pick a shape such as stCircle or stSquare. These shapes have funny-looking Hungarian squiggles prefixed to them because they are taken directly from the source code of the program at runtime rather than typed in by the programmer at design time. In the next few paragraphs I explain how to use that control, primarily because it also contains an interesting description of how to use Run Time Type Information.
You would think that you need to explicitly type in the names of the shapes that appear in the combo box. For instance, you might assume you need to manually type in the words stCircle and stSquare, and so on. These are the same names you see listed in the Object Inspector for the Shape1 object under the property called Shape, and they are the same words that appear in the combo box at runtime. In other words, if you highlight Shape1 on the form and look at the Shape property in the Object Inspector, you find a list of the possible shapes that can be associated with this object. These are the same shapes you should list in the combo box.
As I said earlier, it's not possible to get access to the Object Inspector at
runtime. As a result, if you wanted to explicitly type in the names, you would need
to
first pop open the Property editor for the Items property, and then manually
type these names. To get started, first highlight the combo box on the form by clicking
it. Then, double-click the right side of the Items property in the
Object
Inspector, or you can click the [...] button on the Strings property once.
This pops up a String list editor, as shown in Figure 2.8.\
FIGURE 2.8.
The String list editor enables you to type in a set of default names that
appear in a combo box.
NOTE: In the bottom-left corner of the String list editor is a button that says "Code Editor." If you press this button you can edit your list inside the regular editor for the IDE. This can be especially useful with SQL query statements, which actually provide syntax highlighting. You access the SQL list editor from inside a TQuery component as explained in Chapter 10, "SQL and the TQuery Object."
The actual items that you would type into the String list editor are shown next. Be sure to type them in exactly as shown, and in the identical order.
stRectangle stSquare stRoundRect stRoundSquare stEllipse stCircle
Now when you run the program, the user can drop down this list and select a shape. Here is the code associated with the OnClick event for the combo box:
void __fastcall TForm1::ComboBox1Change(TObject *Sender) { Shape1->Shape = TShapeType(ComboBox1->ItemIndex); }
As you can see, there is no significant difference between this code and the code written in response to a click on the shapes listed in the menu. Of course, in this case you don't have to perform a dynamic cast, but that is a mere bagatelle.
The interesting thing about this second version of the ShapeDem program is the code that appears in the constructor for the form:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { int i; PPropInfo PropInfo = GetPropInfo(PTypeInfo(ClassInfo(__classid(TShape))),"Shape"); for (i = 0; i < 6; i++) ComboBox1->Items->Add(GetEnumName(PropInfo->PropType, i)); ... // Code irrelevant to this example omitted here. }
This code uses RTTI to retrieve the names of the enumerated type that underlies the Shape property for the TShape object.
All VCL classes are illuminated with type information. You can find out virtually anything you want about these published properties of a class at runtime merely by asking through one or more of the functions in the TypInfo unit.
RTTI is an essential part of RAD programming because there must be a way for the Object Inspector to query an object about its published properties. For instance, the Object Inspector has to list the names of the TShapeType so that you can select them from the Property editor for the Shape property. The code shown in the constructor for the main form of this application uses the same technique to retrieve this information that the Object Inspector uses. It would not do to have BCB developers type in this information manually before releasing the project because the Object Inspector must be able to retrieve this information for any component, including the ones that you create or find after purchasing the product.
Are classes illuminated with RTTI larger than non-illuminated classes? You bet! RTTI is bought at a price. However, it is a price that I am willing to pay for two reasons:
In this section I take a very quick look at a second program that uses many of the same controls from the first program. This is just an example, but it is interesting in that it involves using at least one simple Windows API function, or rather, macro. It's important to understand that you can, of course, call the Windows API whenever you want.
Whenever a TShape component is painted, its interior and border are drawn in particular, predefined colors. By default, these colors are white and black, respectively. More specifically, the interior of the ellipse is filled with the color of the currently selected brush. You can change this color by making an assignment of the following type:
Shape1->Brush->Color = MyNewColor;
The RGBSHAPE program on your disk shows how you can get very specific control over the colors of an object that you paint to the screen. The letters RGB stand for red, green, and blue; each of the these colors makes up one of the colors passed to the Windows API RGB macro itself:
COLORREF RGB( BYTE bRed, // red component of color BYTE bGreen, // green component of color BYTE bBlue // blue component of color );
The parameters passed to this function describe an intensity to be assigned to one of these three colors. These numbers always exist within a range between 0 and 255.
If you pass the RGB function the following parameters, it will return a long integer representing the color red:
void __fastcall TForm1::Button1Click(TObject *Sender) { int Red; Red = RGB(255, 0, 0); Shape1->Brush->Color = TColor(Red); }
The Color property of a TBrush object is of type TColor, but TColor is nothing but an enumerated type ranging over all the possible values that can be returned by the RGB macro.
NOTE: You can call the entire Windows API from inside BCB. However, most of the time your calls to the Windows API will be mapped through WINDOWS.HPP, which is a wrapper around the Pascal version of these calls. The Object Pascal team did not translate all the headers for the Windows API. As a result, when you call some of the more obscure or some of the most recent Windows API functions, you may have to explicitly include the header files for these calls. For instance, there is no Pascal translation of the headers for DirectX. As a result, you must include DDRAW.H, and so on in your project if you want to make calls to DirectDraw or other DirectX functions. The vast majority of Windows API calls are in WINDOWS.HPP, however, and that unit is included in all your projects automatically, so you don't have to #include anything in your projects. Needless to say, all Windows API calls are just mappings into a DLL, and it doesn't matter one wit whether you are mapped into the call through Pascal or C++. The compiler does the exact same thing in both cases, and there is no difference in performance, size, and so on.
Here's how you get the colors green and blue:
Green = RGB(0, 255, 0); Blue = RGB(0, 0, 255);
If you combine these three colors in various ways, you can produce particular shades. For instance, if you drop a button into a form and respond to a click on the button with the following code, you draw a bright yellow ellipse on the screen:
Shape1->Brush->Color = RGB(255, 255, 0); Shape1->Shape = stEllipse;
To achieve the color gray, pass in the following parameters:
Gray = RGB(127, 127, 127);
To get a fleshlike color, enter
Skin = RGB(255, 127, 127);
You see how it works. Remember that the first parameter controls the amount of red in the final color, the second the amount of green, and the third the amount of blue. RGB: red, green, blue!
The RGBSHAPE program has a TShape component, three labels, three scrollbars, and three edit controls.
The RGBSHAPE program has only one method:
void __fastcall TForm1::ScrollBar1Change(TObject *Sender) { Shape1->Brush->Color = RGB(ScrollBar1->Position, ScrollBar2->Position, ScrollBar3->Position); Edit1->Text = IntToStr(ScrollBar1->Position); Edit2->Text = IntToStr(ScrollBar2->Position); Edit3->Text = IntToStr(ScrollBar3->Position); }
This method first uses the current positions of the scrollbars to assign a color the TShape objects brush. To make this work correctly, I set the Max property for each scrollbar to 255. When the color has been drawn on the screen, I show the actual numbers passed to the scrollbar in the edit components.
The point of the RGB program is to give you a graphical representation of the way the RGB function works. You might also find that this program helps you choose colors that you want to use in your own programs.
NOTE: When working with the RGBSHAPE program, some users may find that Windows cannot create pure tones for some colors, but instead creates a kind of patchwork that approximates the shade described by the parameters passed to the RGB function. However, you can generally get pure tones if you set each of the parameters to 0, 128, or 255. Numbers halfway between 0 and 128 also usually produce pure tones. Of course, the actual results you see depend on whether you are using a 16-color card, 256-color card, or some video card that offers many thousands of colors. You also need to have the correct video drivers in place. For instance, you don't want to be using a powerful video card and get only 16 colors from it simply for want of the right driver! If you suspect your card is not performing correctly, get on the Web, visit the home page of the company that makes your video card or computer, and download the latest drivers. In Windows 95, you can usually simply unzip the drivers into a temporary directory, right click the desktop, and select Properties | Settings | Change Display Type. When Windows asks for your new drivers, simply point it at the directory where you unzipped the files and it will add them to the system automatically. Be sure to test your work before you restart Windows!
Listing 2.4. The code for the main unit in the RGBSHAPE program.
#include <vcl.h> #pragma hdrstop #include "Main.h" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } void __fastcall TForm1::ScrollBar1Change(TObject *Sender) { Shape1->Brush->Color = RGB(ScrollBar1->Position, ScrollBar2->Position, ScrollBar3->Position); Edit1->Text = IntToStr(ScrollBar1->Position); Edit2->Text = IntToStr(ScrollBar2->Position); Edit3->Text = IntToStr(ScrollBar3->Position); }
In this chapter you have been introduced to the basic facts about Borland C++Builder. In particular, you learned about the environment, manipulating components, and some of the basic tricks of RAD programming. You also had a brief introduction to some advanced topics such as RTTI.
By now the boards are clear to head into some more technical subject matter, such as the examination of extensions to the language featured in the next chapter. After that you will look at events, exceptions, graphics, and sharing code with Delphi. By the time you have finished all these sections, you will have a solid base from which you can launch an exploration of how to use BCB to create cutting-edge Windows applications.
There is so much information to give out in a book like this that I sometimes forget to mention how much I love this programming environment. All the information I am presenting here adds up to a tool that is more powerful, more flexible, and easier to use than any other C++ compiler on the market. Furthermore, this is just the 1.0 version of a product that should become the environment of choice for serious programmers during the next few years.
©Copyright,
Macmillan Computer
Publishing. All rights reserved.