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:
- The IDE
- Projects
- The VCL
- Core VCL Technologies: Components, Properties, and Events
- Stepping into the Pascal source for the VCL
- The syntax of the VCL
- An overview of the component architecture
- A brief look at Run Time Type Information (RTTI)
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
- Think that you have to know it in order to complete BCB programs.
- Think that you have to know it in order to feel at all comfortable inside
the BCB programming environment.
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:
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. |
You can use the IDE to produce Windows executables, DLLs, or console
applications:
- To create a 32-bit Windows executable, you need do nothing. The default
behavior of the IDE is to generate this type of executable.
- To create a DLL, go to File | New and select DLL from the Object
Repository. Click OK. Everything else is automatic. I discuss exporting
functions from a DLL in several later chapters of the book, including Chapter
26, "Extending an Internet Server with ISAPI." In a word, the key to exporting
a method from a DLL is to use __declspec(dllexport), as described in
Chapter 26 and in the comments at the top of the file ISAPI1.CPP from
Chapter 26.
- To create a console application, go to the Options | Project | Linker
option from the menu and select Console Application. If you want to create a
console application as a quick way to produce text output, you should consider
using the BCB TMemo, TListBox, and TEdit controls
instead. I can output text to these controls at least as quickly as I can use
printf or cout. I find these components are more useful than
outputting text to the command line, because they support concepts like
scrolling and saving their contents to a file or the clipboard.
-
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.
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.
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 |
When browsing this table, take special notice of the incremental linker files.
These four files, all of which begin with ILX, are huge. They are the key files
needed to produce BCB's incredibly fast link times. If you turn incremental
linking off, as explained later in this chapter, these files will disappear.
Turning incremental linking off means that your compilation and link times will
be from 5 to 10 times slower than they are with incremental linking turned on.
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.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.
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:
- Use the Control key plus the arrow keys to move the location of a
component one pixel at a time. In particular, drop a component such as a
button on a form. Select it with the mouse. Hold down the Control key and
press the left arrow key.
- Repeat the previous steps, only this time use Shift plus the arrow keys to
resize a component one pixel at a time.
- Hold down the Shift key when selecting a component from the Component
Palette if you want to drop multiple copies of it on a form without having to
go back to the palette. Click the "arrow" icon at the far left of the
Component Palette to break out of this process.
- Become familiar with the Align and Size options from the Edit menu.
Quickly drop down five buttons on a form, each one below the last, without
taking the time to carefully align their right and left sides. Hold down the
Shift key, then select the five buttons with the mouse by clicking on each
one. Now use the Align dialog from the Edit menu to align the tops or sides of
all the selected controls.
- A second technique for selecting a large group of components is to click
on the main form and hold down the left mouse button while dragging over
several components. When you let up the mouse, you will find the components
are selected. If the components you want to select are resting on top of
another control such as a panel, then you will need to hold the control button
down before and during the process of dragging the mouse.
- When designing a form, make it as large as you want. Then, when you have
everything in place, use the Scale option from the Edit menu to make your form
as small as you want, while still keeping each item on the form in proportion.
- Right-click a component, as shown in Figure 2.4, to bring up a menu that
lets you change the size, alignment, tab order, scaling, or creation order.
FIGURE 2.4.
Right-clicking a TTable object to bring up a list of custom options.
Making the Most of the IDE
Here are some tips on using the IDE:
- Right-click almost anything in the IDE to bring up a menu to configure the
component or tool you are currently using. For instance, right-click the
colorful toolbar at the top left of the environment. Select Properties from
the popup menu to bring up the Toolbar editor. Now drag a colorful button from
the toolbar onto the Toolbar editor and grab another button from the ToolBar
editor and drag it back onto the toolbar. I sometimes delete the Trace, Trace
Into, and Pause buttons from the extreme right of the toolbar and replace them
with Copy, Cut, and Paste buttons or with compiler buttons such as Make, Build
All, and Compile Unit.
- Notice that you can view the Project Source and Project Makefile from the
View menu. The project source contains the WinMain() or main()
block for your code. You rarely will need to edit this code. Its contents are
usually configurable from the Options | Project menu. For instance, you can
change the code in the Project Source from Forms page of the Options | Project
menu.
- Check out the Project Manager, which is also available from the View menu.
- You can lock the controls on a form by selecting the Lock Controls option
from the Edit menu. If you have spent some time working on a form and want to
be sure that it does not accidentally get changed, select Lock Controls, and
it will not be possible to move the controls on that form, or any other, until
you deselect this option.
- If you want to set the text on the editor page to read only, right-click
the editor page and select Read Only.
- Watch out for the AutoScroll property of a form. This is set to
true by default, but it can bite back if you leave it set to true
and then move your form to a different resolution. For instance, if you move
from 640x480 to 800x600, you generally should not have AutoScroll set
to true, especially if your form is populated with a large number of
controls.
- Use the Position property of a form to decide whether the form
should first appear in the screen center, at the place where you positioned it
at design time, or at a default location defined by Windows.
- Use the WindowState property of a form to decide if the form
should first be shown minimized, maximized, or at normal size.
- Before releasing a project to the public, check out its appearance in at
least three different resolutions and make sure at least one of them toggles
the font from Small Fonts to Big Fonts, or vice versa. (You can change
Windows' resolution by right-clicking the desktop, selecting Properties, and
going to the settings page. Make sure you change between Big Fonts and Small
Fonts during your testing!)
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.
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.
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.
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.
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.
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:
- Turn the Use tab character option on or off, depending on your liking. (I
prefer to turn it off, so I always know exactly what my code will look like
regardless of the editor or tab settings I happen to use.)
- Decide what tab stops you want to use. I set mine to 3 and 5.
- Choose the Editor speed setting you want. You can choose between the
Default keymapping, IDE Classic, Brief, or Epsilon emulations.
- Go to the Colors page and set the colors you want to use in the editor.
- Consider setting Undo after save to true so that you can keep a buffer of
changes you make, even after you save.
- There are third-party tools such as SlickEdit, MultiEdit, and CodeWright
that have some degree of integration with C++Builder. However, none of these
products is able to work as closely with the IDE as one would like, due to
limitations in the current Tools API for BCB.
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 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.
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:
- Right up front you have components and properties.
- Buried a little deeper you have the delegation model, which involves
events and method pointers.
- Tying the whole picture together are exceptions and a very sophisticated
RTTI system located in a file called TYPINFO.HPP. Most programmers
rarely have to deal with this side of the product, but it is part and parcel
of what makes the tool possible in its current form.
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. 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.
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 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.
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 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.
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.
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.
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.
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.
Calling Run: Finding the Message Loop
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:
- You need to know this information if you are going to use BCB for serious
work. You have to understand how your application is launched, where the
message loop is, and how forms are allocated.
- There has to be some time when you actually see the place where C++ and
Object Pascal meet. So now you've seen it. The point I hope you take home from
this is that there just plain isn't any real difference between C++ and Object
Pascal. There may be a difference between ANSI C and ANSI Pascal, but C++ and
Object Pascal are the same. Certainly there is no significant difference in
the way the two languages perform or in the capabilities of the two languages.
It should also now be obvious that the compiler just doesn't care which
language you use. I mix and match C++ and Object Pascal all the time. I know
both languages well enough so half the time I am not even conscious of which
language I'm using. You can find a language bigot on every street corner.
What's rare is to find someone who can look beneath the surface and see that
at bottom the two languages are virtually identical. The differences are
almost all superficial, and they tend to cancel each other out. C++ does some
things better than Object Pascal, and Object Pascal does some things better
than C++. Who cares? The point is they are both great languages. Now you can
use them both together without penalty, so you can take the best from both
worlds.
Creating the ShapeDem Program
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:
- They do not force you to subclass a control, nor inherit from a class in
order to override one of its properties. For instance, you can change an edit
control's behavior without subclassing the control or descending from an OWL
or MFC type object that wraps the control.
- They enable you to forgo the ugly, lengthy, and confusing switch
statement found in standard window procedures.
- It provides for contract-free programming. You can do whatever you want in
an event handler. There are no rules binding what you can and cannot do inside
an event handler. Any code you can write legally elsewhere in a program you
can also write in an event handler.
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.
A First Look at RTTI and TypInfo
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:
- It makes RAD possible, as described previously in the text that explains
how the property types are listed in the Property editor.
- It enables me to query objects at runtime. Querying objects at runtime
gives me the ability to create generic code that will work automatically,
regardless of the type of object I pass to it. For instance, the RTTI example
using dynamic_cast shown previously explains how you can safely test
the type of an object before performing an action on it. You will see many
examples of that type of code in this book, including cases where you can pass
an entire form to a routine, and ask that routine to perform some function on
all the controls of a certain type on that form. Generic code of that type can
be tested once and then used over and over again with multiple objects. The
ability to talk to an object and ask it its type gives us the ability to
manage objects automatically. This can be especially useful in Ole Automation,
and particularly in DCOM. For instance, you can start wandering about on a
network, looking for objects. If you find one, you can use RTTI to ask it
questions about itself, and to query its capabilities!
Using the RGB Function
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);
}
Summary
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.
|