Basic Facts About C++Builder
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
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.
Creating
C++Builder Projects
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:
NOTE:
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.
BCB File
Extensions
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.
The
include and lib Directory Paths Issue
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.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.
Working in
the IDE
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.
Tips on
Manipulating Controls
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:
Making the
Most of the IDE
Here are some tips on
using the IDE:
Project
Options
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. Environment Options
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. 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. Feeling at Home in
the IDE
To help make the IDE comfortable,
you might go to the Options | Environment | Editor page, shown in Figure 2.5. From the Editor page you can make
the following changes:
Converting Forms to
Text and Back
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
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.
Core Technology:
Components, Properties, Delegation
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.
Why the VCL?
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.
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.
Come on Charlie, Tell Us What You Really Think!
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
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
Using 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
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. 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. #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.
The Project Source:
Where Pascal Meets the C
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. 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.
Initializing the Application
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.
Creating Forms
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. 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.
Calling Run: Finding
the Message
|
|