C++Builder and the VCL - Part 03
are
definitely in the low-rent district right now, and so perhaps a few more
words of explanation are necessary. Here is
a routine that uses both open arrays and sprintf to solve the same problem: void __fastcall TForm1::Button1Click(TObject
*Sender) { char S[150];
if
(MoreInput->ShowModal() == mrOk) { sprintf(S,
"Hello, my name is %s, and I'm %d years old.",
MoreInput->Name, MoreInput->Age);
Edit1->Text = S;
Edit2->Text = Format("Hello, my name is %s, and I'm %d years
old.",
OPENARRAY(TVarRec, (MoreInput->Name, MoreInput->Age))); } } In
showing your comparative examples of sprintf and Format, this function makes use of a
second form called TMoreInput. I will discuss this form in the
next section of the chapter. For now, however, I will forgo discussing
multiform projects, and will concentrate instead on using OpenArrays. As you
can see, it doesn't really take any more code to use Format and OPENARRAY than it does to use sprintf. In fact, both techniques have their
disadvantages. The sprintf method requires you to declare a
temporary variable, the Format method gives you a good dose of
the wordy OPENARRAY macro. However, neither technique
is particularly difficult to implement. When
using OPENARRAY, you usually pass TVarRec as the first argument of the macro, and then
pass a comma-delimited set of variables in the second argument. The entire
second argument should be set off by parentheses. Here are a few rather
artificial examples of how to use the macro: OPENARRAY(TVarRec, (1)) OPENARRAY(TVarRec, (1, 2)) OPENARRAY(TVarRec, (1, 2, 3)) And so
on, up to, but not beyond, 18. The point is that you pass the
variables you want to have processed in the second argument to the macro. You
can pass any type in this section, because the macro itself doesn't care. The
method that is using the macro may not be so forgiving, however, so you need
to put some thought into the code you write. For instance, Format takes only certain types of parameters, and
there would be no point in passing something to OPENARRAY that the Format function could not process. The
same holds true of sprintf. There would be no point in
passing the wrong type of parameter to it, because the format specifiers
handle only certain types. The OPENARRAY macro itself is very simple: #define OPENARRAY(type, values) \
OpenArray<type>values,
OpenArrayCount<type>values.GetHigh() As you
can see, it takes two separate object templates to make the array work. (In
fact, TVarRec is a third object, so it takes
three objects in all to bring this business home for dinner.) If you are
interested, you can open up SysDefs.h and see how this technology is
implemented. For the purposes of this book, however, I will simply say that
this is one of those cases where you should "pay no attention to the man
behind the curtain." I just let the macro do its work and concentrate
instead on the parts of the program that are under my jurisdiction. That is
all I'm going to say about OpenArrays. As I conceded earlier, this is
not really the garden spot of BCB programming. However, the code I've shown
you does get the job done. In particular, the point of this OpenArray business is that it enables you to pass a
variant number of parameters to a function. This is a pretty slick trick in
itself, and some programmers will probably be able to find occasions when
they can use this technology for their own purposes. Its main value, however,
is simply that it enables you to use the parts of the VCL that are expecting
an array of const. Using Two
Forms in One Program The
sample function shown above called Button1Click
has a line in it where a second form is launched: if (MoreInput->ShowModal() == mrOk) The
program uses this second form to retrieve the name and age input from the
user. Popping up
a form to get information from a user is a common task in BCB, so I should
perhaps take a moment to explain how it works. To get
started, you should use the File menu, or some other tool, to create a second
form for your project. Save it into the same directory where your current
project is located. Add two edit controls labeled Name and Age to the second form, and place two
TBitBtns on the form, as shown in Figure
3.2. Use the Kind property of the BitBtns to make one an OK button and the second a
Cancel button. Notice that the ModalResult property of the OK TBitBtn is automatically set to mrOk, and the ModalResult property of the Cancel TBitBtn is automatically set to mrCancel. Figure 3.2.The form for the TMoreInput dialog has two labels, two edit
controls, and two TBitBtns. Now
create two properties that users of the form can use when they need to access
the input from the user: class TMoreInput : public TForm { // Code
omitted here private: AnsiString
FName; int FAge; public: virtual
__fastcall TMoreInput(TComponent* Owner); __property
int Age={read=FAge, write=FAge}; __property
AnsiString Name={read=FName, write=FName}; }; As you
can see, the Age and Name properties front for two private
variables of the object. These variable are assigned to the user's input if
the user presses the OK button: void __fastcall TMoreInput::OkBtnClick(TObject
*Sender) { Name =
Edit1->Text; Age =
Edit2->Text.ToInt(); } It is
now time to go full circle and look back at the stripped-down version of the
function that makes use of this dialog: void __fastcall TForm1::Button1Click(TObject
*Sender) { if (MoreInput->ShowModal()
== mrOk) {
Edit2->Text = Format("Hello, my name is %s, and I'm %d years
old.",
OPENARRAY(TVarRec, (MoreInput->Name, MoreInput->Age))); } } The Button1Click method calls the TForm ShowModal method to launch the dialog. If
the user presses the OK button, the input is retrieved from the TMoreInput dialog through the officially designated
properties. This enables you to use the private data of TMoreInput without forcing TMoreInput to refrain from ever changing its implementation. For instance, TMoreInput could someday use a struct that contains both the Name and Age fields, but could continue to give users of the form access to data
through the Name and Age variables. If you
are interested in this program, you can find it on disk in the Chap02 directory as a file called OpenArrays1. The program itself is simple, but it gives
a good illustration of how to use a second dialog to retrieve information
from the user. This is a common task that will see many variations run on it
in different parts of this book. Working with
the Set Object Sets are
used widely in BCB. Here for instance, is the MsgDialog routine, which is called frequently but requires some knowledge of
sets before you can use it correctly: AnsiString S("While Lust is in his pride, no
exclamation \n"
"Can curb his heat or rein his rash desire."); MessageDlg(S, mtInformation, TMsgDlgButtons()
<< mbOK, 0); The VCL makes
heavy use of sets, so there are a number of places in standard BCB database
or interface code where you will need to take advantage of the syntax you see
here. The goal of the next few pages of this chapter is to explore the BCB
Set class and reveal its inner workings. Sets are
easy to understand if you just take a few minutes to absorb their syntax.
This is, however, one of those subjects that you have to master if you want
to use BCB. In fact, you will find many parts of BCB cumbersome if you don't
understand this template class. Here is
a "pseudo template" for the header of the set class designed to
help you see how these sets are structured: Set<Type, MinimumValue, MaximumValue> More
precisely, here is the more formal header declaration from SysDefs.h: template<class T, unsigned char minEl, unsigned
char maxEl> To
declare a set, you write the word Set followed
by an open bracket, the type of your set, and its minimum and maximum values.
For instance, if you want to create a set of all small letters, you would
write Set<char, `a', `z'> SmallLetters; To
create the set of all numbers between 0 and 9, you might write Set<int, 0, 9> SmallNumbers; In the
first case, MySet does not consist of the letters a and z, but of a, z, and all the letters between them. In other words, the letter a is the smallest value in the set and the
letter z is the maximum value in the set,
as shown previously in the formal declaration for the template class. Both SmallLetters and SmallNumbers are declarations of objects. Often, you
don't want to create a new object, but create a new object type with the typedef keyword: typedef Set<int, 0, 9> TSmallNumbers; Now you
have a type you can work with, and can therefore declare separate instances
of this type: TSmallNumbers SmallNumbers1; TSmallNumbers SmallNumbers2; The
point here is that the first numeric set declared previously, the one called SmallNumbers, is an instance of an object, while TSmallNumbers is the declaration for a type. NOTE: I'm going
to break a rule of this book and spend a few moments on standard C syntax. In
particular, I'm going to look at typedefs,
because this is an important and easily misunderstood part of the language.
The typedef keyword enables you to declare a
type within a particular scope. Unfortunately, the #define directive performs a function similar to the typedef keyword, even though the two pieces of syntax have different ways of
implementing the result. typedef float REAL; #define float REAL One asks
the compiler to do its work for it, while the second asks the preprocessor to
do the work. The result, however, is similar. That is, they give you a new
"type" to work with called REAL. Now that
you know the basic facts about sets, it's time to look at some programs that
give them a workout. These are important examples, so I will feature them in
their own section of the chapter. Two Sample
Programs for Working with Sets The
sample program shown in Listings 3.3 and 3.4, called NumberSets, gives you a
look at the basic syntax for creating sets. It is followed by a second sample
program, called SetBasics, that shows how to create the
union, intersection, and difference of two sets. NOTE: The
Set sample programs used in this book originally included some sample components
that are found in the Examples directory that ships with BCB.
These components were found in Listing 3.3. The header for the
NumberSets program. //--------------------------------------------------------------------------
#ifndef MainH #define MainH //--------------------------------------------------------------------------
#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\Spin.hpp> //--------------------------------------------------------------------------
typedef
Set<int, 0, 9> TSmallNum; class TForm1 : public TForm { __published: TButton
*AddToSetABtn; TListBox
*ListBox1; TSpinEdit
*SpinEdit1; TButton
*RemoveFromSetBtn; void
__fastcall AddToSetABtnClick(TObject *Sender); void
__fastcall RemoveFromSetBtnClick(TObject *Sender); private: TSmallNum
NumSet; void
__fastcall ShowSet(TSmallNum ANumSet, int ListBoxNum); public: virtual
__fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------
extern TForm1 *Form1; //--------------------------------------------------------------------------
#endif Listing 3.4. The main form for
the NumberSets program. #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) :
TForm(Owner) { } void __fastcall TForm1::ShowSet(TSmallNum ASmallNum,
int ListBoxNum) { AnsiString
S("Set <"); for (int i =
0; i < 275; i++) { if
(ASmallNum.Contains(i)) S += "`" + AnsiString(i) +
"`,"; }
S.SetLength(S.Length() - 1); S +=
">"; if
(S.Length() == 5) S =
"NULL Set"; switch
(ListBoxNum) { case 1:
ListBox1->Items->Add(S); break; default:
ShowMessage("Error
specifying listbox"); } } void __fastcall TForm1::AddToSetABtnClick(TObject
*Sender) { int i =
SpinEdit1->Text.ToInt(); NumSet
<< i;
ShowSet(NumSet, 1); } void __fastcall TForm1::RemoveFromSetBtnClick(TObject
*Sender) { int i =
SpinEdit1->Text.ToInt(); NumSet
>> i;
ShowSet(NumSet, 1); } typedef
Set<int, 0, 9> TSmallNum; TSmallNum NumSet; The main
function of the program is to show how to move items in and out of a set. The
AddToSetABtnClick method retrieves the number the
user places in a TSpinEdit control: int i = SpinEdit1->Text.ToInt(); It then
uses the << operator to insert the new number
into the set: NumSet << i; A
similar method shows how to move information out of the set: int i = SpinEdit1->Text.ToInt(); NumSet >> i; The most
complicated code in the program involves a method called ShowSet that creates a picture of the set and shows
it to the user. The heart of the ShowSet method
uses the Contains method of BCB's Set class to see if a particular element is
contained in the set: if (ASmallNum.Contains(i)) Contains returns True if the element is part of the set, and it returns False if the element is not in the set. The
results of this effort are shown to the user in a TListBox, as illustrated in Figure 3.3. Figure 3.3.The NumberSet program creates a
visual image of a set so you can get an accurate feeling for how sets work. The
SetBasics program takes the principles used in the NumberSet example and
pushes them a little further so you can get a clear sense of what it means to
create the intersection, union, or difference of two sets. (See Figure 3.4.)
The code for the program is shown in Listing 3.5 and 3.6. Figure 3.4.The SetBasics program shows how to
find the union, difference, or intersection of two sets. Listing 3.5. The header for the
SetBasics program. //--------------------------------------------------------------------------
#ifndef MainH #define MainH //--------------------------------------------------------------------------
#include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> #include <vcl\Menus.hpp> //--------------------------------------------------------------------------
typedef Set <char, `A', `z'> TLetterSet; class TForm1 : public TForm { __published: TEdit
*Edit1; TButton
*AddToSetABtn; TListBox
*ListBox1; TButton *AddToSetBBtn;
TListBox
*ListBox2; TListBox
*ListBox3; TMainMenu
*MainMenu1; TMenuItem
*Options1; TMenuItem
*Union1; TMenuItem
*Intersection1; TMenuItem
*Difference1; void
__fastcall AddToSetABtnClick(TObject *Sender); void
__fastcall AddToSetBBtnClick(TObject *Sender); void
__fastcall Union1Click(TObject *Sender); void
__fastcall Intersection1Click(TObject *Sender); void
__fastcall Difference1Click(TObject *Sender); private: TLetterSet
LetterSetA; TLetterSet
LetterSetB; void
__fastcall ShowSet(TLetterSet ALetterSet, int ListBoxNum); public: virtual
__fastcall TForm1(TComponent* Owner); }; //--------------------------------------------------------------------------
extern TForm1 *Form1; //--------------------------------------------------------------------------
#endif Listing 3.6. The main form for
the SetBasics program. #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) :
TForm(Owner) { } void __fastcall TForm1::ShowSet(TLetterSet
ALetterSet, int ListBoxNum) { AnsiString
S("Set <"); for (int i =
0; i < 275; i++) { if (ALetterSet.Contains(char(i)))
S +=
"`" + AnsiString(char(i)) + "`,"; }
S.SetLength(S.Length() - 1); S +=
">"; if
(S.Length() == 5) S =
"NULL Set"; switch
(ListBoxNum) { case 1:
ListBox1->Items->Add(S); break; case 2:
ListBox2->Items->Add(S); break; case 3:
ListBox3->Items->Add(S); break; default:
ShowMessage("Error specifying listbox"); } } void __fastcall TForm1::AddToSetABtnClick(TObject
*Sender) { AnsiString
S(Edit1->Text); char ch =
S[1]; LetterSetA
<< ch;
ShowSet(LetterSetA, 1); } void __fastcall TForm1::AddToSetBBtnClick(TObject
*Sender) { AnsiString
S(Edit1->Text); char ch =
S[1]; LetterSetB
<< ch;
ShowSet(LetterSetB, 2); } void __fastcall TForm1::Union1Click(TObject *Sender)
{ TLetterSet
Union; Union =
LetterSetA + LetterSetB; ShowSet( } void __fastcall TForm1::Intersection1Click(TObject
*Sender) { TLetterSet
Intersection; Intersection
= LetterSetA * LetterSetB;
ShowSet(Intersection, 3); } void __fastcall TForm1::Difference1Click(TObject
*Sender) { TLetterSet
Difference; Difference =
LetterSetA - LetterSetB;
ShowSet(Difference, 3); } //-------------------------------------------------------------------------- typedef Set <char, `A', `z'> TLetterSet; The code
for moving elements in a set has not changed significantly from the NumberSet
example: void __fastcall TForm1::AddToSetABtnClick(TObject
*Sender) { AnsiString
S(Edit1->Text); char ch =
S[1]; LetterSetA
<< ch;
ShowSet(LetterSetA, 1); } void __fastcall TForm1::AddToSetBBtnClick(TObject
*Sender) { AnsiString
S(Edit1->Text); char ch =
S[1]; LetterSetB
<< ch;
ShowSet(LetterSetB, 2); } The difference
here, of course, is that two sets are being used, and that you can only add
elements to each set. NOTE: Just
before the release of C++Builder, the team changed the way the first
character of an AnsiString is accessed. It is now indexed at
the first position in the array, rather than at position zero: char ch = S[1];
// First character of string char ch = S[0];
// References nothing, incorrect reference! This
change came so late in the product cycle that I may not reference it correctly
in all cases where the subject is brought up in this text. However, I should
have caught all references in the code that appears on the CD-ROM that
accompanies this book. Once you
have created two sets that interest you, you can press a button on the
applications form to see the union, difference, or intersection of the sets.
Here, for instance, is how to create the intersection of two sets: void __fastcall TForm1::Intersection1Click(TObject
*Sender) { TLetterSet
Intersection; Intersection
= LetterSetA * LetterSetB;
ShowSet(Intersection, 3); } The * operator of the Set template is used to perform the operation. You use the + operator to find the union of two sets and
the - operator to show the difference between two
sets: Union = LetterSetA + LetterSetB; Difference = LetterSetA - LetterSetB; The main
form for the program is shown in Listing 3.6. Suppose
you have two sets that look like this: <`A', `B', `E'> <`B', `G'> The
union of the sets would look like this: <`A', `B', `E', `G'>. The
intersection would look like this: <`B'>. The
difference, if you subtracted the second from the first, would look like
this: <`A',
`E'>. You can
experiment with the program further if you want to see more about creating
the intersection, union, or difference of two sets. If you look in SysDefs.h or in the online help, you will see that
there are also operators that enable you to test for equality or to see if
one set is larger than another. A Few
Additional Points about Sets You can
add multiple items to a set with the following syntax: NumSet << 2 << 3 << 5; Here,
for instance, is a real-world case of adding or removing items from a TNavigator control: FMyNavigator->VisibleButtons =
TButtonSet() << nbFirst << nbLast << nbNext <<
nbPrior; In
general, sets are very easy to use, though their syntax is not particularly
inviting at first glance. Once again, it is important to remember that this Set class is meant for use with the VCL. The Set class in SysDefs.h is not a general purpose set class, and it is not meant as a
substitute for the code you find in the Standard Template Library. As with
much of the code shown in this chapter, the key point that justifies the
existence of the Set class is its compatibility with
certain features of the VCL. Two such features have been illustrated in the
last few pages. The first illustrative example is the preceding FMyNavigator code, and the MsgDialog code shown back at the beginning of this discussion of sets: MessageDlg(S, mtInformation, TMsgDlgButtons()
<< mbOK, 0); TMsgDlgButtons is a set class. It has the
following declaration: enum TMsgDlgBtn { mbYes, mbNo, mbOK, mbCancel,
mbAbort,
mbRetry, mbIgnore, mbAll, mbHelp }; typedef Set<TMsgDlgBtn, mbYes, mbHelp> TMsgDlgButtons; It
should now be obvious how to use this set. The elements of the set consist of
all the items in the TMsgDlgBtn enumerated type. The set itself
ranges from a low value of mbYes to a high value of mbHelp. If you wanted All and Ignore buttons on a MessageBox, you could write the following code to
display it to the user: MessageDlg(S, mtInformation, TMsgDlgButtons()
<< mbAll << mbIgnore, 0); The
second parameter to MessageDlg contains a value from the
following enumerated type: enum TMsgDlgType { mtWarning, mtError,
mtInformation, mtConfirmation, mtCustom }; Use the
Source, Luke! You
might notice that I frequently refer directly to the various header files
that ship with BCB. Getting to know as much as possible about the source for
the VCL and for BCB is a worthwhile endeavor. There are times when I use the
Tool menu to make it even easier to get at source files that I use a lot. You can
configure the Tools menu in BCB to run any program you like. As a result, I
sometimes ask it to start Notepad (also known as Visual Notepad) or some
other editor with the source from a key header file in it. To do this 1. Open the Tools menu and select Configure. The way
this process looks in action is shown in Figure 3.5. Figure 3.5.Using the Tools menu to create a
means for automatically opening a header file whenever you need it. If
possible, and especially if you are using Windows 95, you should find a real
editor to use with large header files. Good ones include Visual SlickEdit
from MicroEdge, MultiEdit from American Cybernetics, and CodeWright from
Premia. Getting the
Heap Status To
understand what is happening in your program, you should become familiar with
the THeapStatus class from SYSDEFS.HPP: class THeapStatus { public: Cardinal
TotalAddrSpace; Cardinal
TotalUncommitted; Cardinal
TotalCommitted; Cardinal
TotalAllocated; Cardinal
TotalFree; Cardinal
FreeSmall; Cardinal
FreeBig; Cardinal Unused;
Cardinal
Overhead; Cardinal
HeapErrorCode; } ; To
retrieve an instance of this type, you can call GetHeapStatus: THeapStatus HeapStatus = GetHeapStatus(); The
values retrieved by a call to GetHeapStatus tell you about the condition of
the heap for your program. They do not refer to global memory for the machine
on which your program is running. This call will probably fail if made from
inside a DLL. Variants Variants
are an OLE-based type in the VCL that can be assigned to a wide range of
variable types. Some people jokingly refer to variants as a typeless type,
because it can be used to represent a string, an integer, an object, or
several other types. The
following is a simple procedure that illustrates the flexible nature of
variants: void __fastcall TForm1::BitBtn1Click(TObject
*Sender) { Variant V; V = 1; V =
"Sam"; } This
procedure compiles fine under BCB. As you can see, you are allowed to assign both
a string and an integer to the same variant variable. The
following procedure is also legal: void __fastcall TForm1::BitBtn1Click(TObject
*Sender) { Variant V; V = 1; Caption =
"Sam" + V; } The
attempt to assign Edit1.Text to a variant concatenated with a
string would have been flagged as a type mismatch by Underneath
the surface, variant types are represented by a 16-byte structure found in SYSDEFS.H. At the time of this writing, that structure
looks like this: class TVarData { public: Word VType;
Word
Reserved1; Word
Reserved2; Word
Reserved3; union { Smallint
VSmallint; Integer
VInteger; Single
VSingle; Double
VDouble;
CurrencyBase VCurrency;
TDateTimeBase VDate; PWideChar VOleStr;
Ole2::IDispatch* VDispatch; Integer
VError; WordBool
VBoolean;
Ole2::IUnknown* VUnknown; Byte
VByte; Pointer
VString;
PVarArray VArray; Pointer
VPointer; }; }; Notice
that this is a union; that is, the various types represented in the switch statement are overlaid in memory. The
structure ends up being 16 bytes in size because the VType field is 2 bytes; the three reserved fields total 6 bytes; and the
largest of the types in the variant section is the double, which is 8 bytes
in size. (2 + 6 + 8 = 16.) Once again, the switch statement in the declaration is not a list of separate fields, but a
list of different ways to interpret the 8 bytes of data contained in the
second half of the space allocated for the record. The
following are the declarations for the values used with variants: #define varEmpty (unsigned char)(0) #define varNull (unsigned char)(1) #define varSmallint (unsigned char)(2) #define varInteger (unsigned char)(3) #define varSingle (unsigned char)(4) #define varDouble (unsigned char)(5) #define varCurrency (unsigned char)(6) #define varDate (unsigned char)(7) #define varOleStr (unsigned char)(8) #define varDispatch (unsigned char)(9) #define varError (unsigned char)(10) #define varBoolean (unsigned char)(11) #define varVariant (unsigned char)(12) #define varUnknown (unsigned char)(13) #define varByte (unsigned char)(17) #define varString (unsigned short)(256) #define varTypeMask (unsigned short)(4095) #define varArray (unsigned short)(8192) #define
varByRef (unsigned short)(16384)
NOTE: When
you study these declarations, it's important to understand that the VCL defines
a certain set of behavior to be associated with variants, but might not
guarantee that the implementation will remain the same from version to
version. In other words, you can almost surely count on the fact that
assigning both a string and an integer to the same variant will always be
safe. However, you might not necessarily be sure that variants will always be
represented by the same constants and record shown previously. To check
whether these implementations have been blessed as being permanent, refer to
your VCL documentation. It
should be clear from the preceding code examples that knowing what type is
represented by a variant at a particular moment can be important. To check
the variant's type, you can use the VarType
function, as shown here: void __fastcall TForm1::BitBtn1Click(TObject
*Sender) { int AType; Variant V; AType =
VarType(V); //
Additional code } In this
example, the Integer variable AType will be set to one of the constants shown previously. In particular,
it will probably be assigned to varEmpty,
because in the preceding example this variable has not yet been assigned to
another type. If you
want to get a closer look at a variant, you can typecast it to learn more
about it, as shown in the following example: void __fastcall TForm1::BitBtn1Click(TObject
*Sender) { Variant V; TVarData VarData; int AType; V = 1; VarData = TVarData(V); AType
= VarData.VType; } In this
particular case, AType will be set to the same value
that would be returned from a call to VarType. To
understand more about how all this works, see Listing 3.7 and 3.8, in which
you can find the code for the VarDatas program. This code sets a single
variant to a series of different values and then examines the variant to
discover the current value of its VType field. Listing 3.7. The VarDatas
program lets you examine variant types. Here is the header file for the main
form of the program. /////////////////////////////////////// // Copyright (c) 1997 by Charlie Calvert // #ifndef MainH #define MainH #include <vcl\Classes.hpp> #include <vcl\Controls.hpp> #include <vcl\StdCtrls.hpp> #include <vcl\Forms.hpp> class TForm1 : public TForm { __published: TButton
*bVariantPlay; TListBox
*ListBox1; void
__fastcall bVariantPlayClick(TObject *Sender); private: void
ShowVariant(Variant &V); public: virtual
__fastcall TForm1(TComponent* Owner); }; extern TForm1 *Form1; #endif Listing 3.8. The main source
file for the VarDatas program includes a routine that returns a string
stating the type of a variant. /////////////////////////////////////// // Copyright (c) 1997 by Charlie Calvert // #include <vcl\vcl.h> #pragma hdrstop #include "Main.h" #pragma resource "*.dfm" TForm1 *Form1; __fastcall TForm1::TForm1(TComponent* Owner) :
TForm(Owner) { } AnsiString GetVariantType(Variant &V) { TVarData VarData; AnsiString S; VarData = TVarData(V); switch
(VarData.VType) { case varEmpty:
S = "varEmpty"; break; case
varNull: S = "varNull"; break; case
varSmallint: S = "varSmallInt";
break; case
varInteger: S = "varInteger";
break; case
varSingle: S = "varSingle"; break; case
varDouble: S= "varDouble"; break; case
varCurrency: S = "varCurrency"; break; case
varDate: S = "varDate"; break; case
varOleStr: S = "varOleStr"; break; case
varDispatch: S = "varDispatch"; break; case
varError: S = "varError"; break; case varBoolean:
S = "varBoolean"; break; case
varVariant: S = "varVariant"; break; case
varUnknown: S = "varUnknown"; break; case
varString: S = "varString"; break; case
varTypeMask: S = "varTypeMask"; break; case
varByRef: S = "varByRef"; break; case
varByte: S = "varByte"; break; case
varArray: S = "varArray"; break; default: S =
"Error"; } return S; } void TForm1::ShowVariant(Variant &V) { AnsiString
S, Temp; if
((VarIsEmpty(V) == True) || (VarIsNull(V) == True)) { Temp =
"Null"; S =
Format("Value: %-15s Type:
%s",
OPENARRAY(TVarRec, (Temp, GetVariantType(V)))); } else { Temp = V; S =
Format("Value: %-15s Type:
%s", OPENARRAY(TVarRec
, (Temp, GetVariantType(V)))); }
ListBox1->Items->Add(S); } void __fastcall TForm1::bVariantPlayClick(TObject
*Sender) { Variant V;
ShowVariant(V); V = Null;
ShowVariant(V); V = 1;
ShowVariant(V); V =
"Sam";
ShowVariant(V); V = 1.25;
ShowVariant(V); } Figure 3.6.The VarDatas program uses the TVarData structure to examine how variants
are put together.
The data
shown in the list box in Figure 3.6 represents the value and internal
representation of a single variant that is assigned a series of different types.
It's important to understand that Variant can't
simultaneously represent all types, and can instead at any one moment take on
the characteristics of only one particular type. The code
that assigns different types to a variant is easy to understand, as is shown
here: void __fastcall TForm1::bVariantPlayClick(TObject
*Sender) { Variant V;
ShowVariant(V); V = Null;
ShowVariant(V); V = 1;
ShowVariant(V); V =
"Sam";
ShowVariant(V); V = 1.25;
ShowVariant(V); } The code
that reports on the current type of a variant is somewhat more complex, but
still relatively straightforward, as shown next: AnsiString
GetVariantType(Variant &V) { TVarData VarData; AnsiString S; VarData = TVarData(V); switch
(VarData.VType) { case
varEmpty: S = "varEmpty"; break; case
varNull: S = "varNull"; break; case
varSmallint: S = "varSmallInt";
break; case
varInteger: S = "varInteger";
break; case
varSingle: S = "varSingle"; break; case
varDouble: S= "varDouble"; break; case
varCurrency: S = "varCurrency"; break; case
varDate: S = "varDate"; break; case
varOleStr: S = "varOleStr"; break; case
varDispatch: S = "varDispatch"; break; case varError:
S = "varError"; break; case
varBoolean: S = "varBoolean"; break; case
varVariant: S = "varVariant"; break; case
varUnknown: S = "varUnknown"; break; case
varString: S = "varString"; break; case
varTypeMask: S = "varTypeMask"; break; case
varByRef: S = "varByRef"; break; case
varByte: S = "varByte"; break; case
varArray: S = "varArray"; break; default: S =
"Error"; } return S; } This code
first converts a variant into a variable of type TVarData. In doing so, it is merely surfacing the true underlying type of the
variant. However, a variable of type TVarData will not act the same as a variable of type Variant. This is because the compiler provides special services for variants
that it would not provide for a simple record type such as TVarData. It's
important to note that there are at least two ways to write the first lines
of code in this function. For instance, I could have written AnsiString GetVariantType(Variant &V) { AnsiString
S; switch
(VarType(V)) { case
varEmpty: S = "varEmpty"; break; case
varNull: S = "varNull"; break; // Code
omitted } } This code
works the same as the code shown in the actual program found on the CD-ROM. However
you decide to implement the function, the key point is that variants can take
on the appearance of being of a certain type. The chameleon-like behavior of Variant is sparked by the type of variable to which
it is assigned. If you want, you can think of a variant as a chameleon that
hides itself from view by assuming the coloration of the variable to which it
is assigned. A variant is never of type Variant; it's always either empty, NULL, or the
type of the variable to which it is assigned. In the same way, a chameleon
has no color of its own but is always changing to adapt its color to the
environment around it. Either that, or it is unborn, dead, nonexistent, or
has no color at all! The
following routines can all be used with variants. To learn more about these
routines, look in the online help. extern void __fastcall VarClear(Variant &V); extern void __fastcall VarCopy(Variant &Dest,
const Variant &Source); extern void __fastcall VarCopyNoInd(Variant
&Dest, const Variant &Source); extern void __fastcall VarCast(Variant &Dest,
const Variant &Source, int VarType); extern int __fastcall VarType(const Variant &V);
extern Variant __fastcall VarAsType(const Variant
&V, int VarType); extern bool __fastcall VarIsEmpty(const Variant
&V); extern bool __fastcall VarIsNull(const Variant
&V); extern AnsiString __fastcall VarToStr(const Variant
&V); extern Variant __fastcall VarFromDateTime(TDateTime
DateTime); extern TDateTime __fastcall VarToDateTime(const
Variant &V); Here are
some routines to use with Variant arrays. extern Variant __fastcall VarArrayCreate(const int
*Bounds, const int Bounds_Size,int VarType); extern Variant __fastcall VarArrayOf(const Variant
*Values, const int Values_Size); extern void __fastcall VarArrayRedim(Variant &A,
int HighBound); extern int __fastcall VarArrayDimCount(const Variant
&A); extern int __fastcall VarArrayLowBound(const Variant
&A, int Dim); extern int __fastcall VarArrayHighBound(const
Variant &A, int Dim); extern void * __fastcall VarArrayLock(const Variant
&A); extern void __fastcall VarArrayUnlock(const Variant
&A); extern Variant __fastcall VarArrayRef(const Variant
&A); extern bool __fastcall VarIsArray(const Variant
&A); Before
closing this section, I want to make it clear that variants are not meant to
be used broadly in your program whenever you need to work with a variable. I
have no doubt that some people will in fact program that way, but I want to
emphasize that the developers didn't really want variants to be used that
way. This is mainly because they have some overhead associated with them that
can slow down the compiler. Some tricks performed by variants, such as string
manipulation, happen to be highly optimized. However, you should never
consider a variant to be as fast or efficient as a standard C type. Variants
are almost certainly best used in OLE and database applications. In
particular, variants were brought into the language because they play a role
in OLE automation. Furthermore, much of the structure and behavior of
variants is defined by the rules of OLE. These unusual types also prove to be
useful in database applications. As a rule, there is so much overhead
involved in referencing any value from a database that a little additional
variant manipulation code is not going to make a significant difference. Working with
Text Files Some of
the best features of C++Builder are not immediately obvious to you when you
first start working with the tool, or even after you have been working with
the tool for awhile. For instance, C++Builder has a built in type called a TStringList that all but makes obsolete the concept of a
text file. This class is so easy to use, and so powerful, that there are few
occasions when you would want to open a text file using fopen. It's important to note that manipulating
text files is not really the central purpose of the TStringList class; however, it performs this task so well that there is little need
to ever resort to using fopen, fclose, and other similar commands. Before
looking at how you would use to TStringList to
open a text file, it might be helpful to point out the places in BCB where
this class is most often used, as well as a few other features of the class. The TStringList class is a descendant of the abstract class TStrings. There are no instances of the TString class in BCB, because this is an abstract
type. However, the TStringList, TListBox->Items, TComboBox->Items, TMemo->Lines, TRichEdit->Lines and TQuery->VCL classes all descend directly from TStrings. This means you can assign instances of
these classes to one another. You can,
for instance, create an instance of the TStringList class and assign it to a TListBox->Items object: TStringList *MyStringList = new TStringList; MyStringList->Add("Some text"); MyStringList->Add("More text"); ListBox1->Items = MyStringList; If you
write code like this, ListBox1 will end up showing two items in
it, with the first being "Some
text", and the
second being "More
text". You can
write the contents of any string list to file by using the SaveToFile method, and you can read the list back using
LoadFromFile. There are also SaveToStream and LoadFromStream methods: MyStringList->SaveToFile("C:\\Sam.txt");
MyStringList->LoadFromFile("C:\\Sam.txt");
TStringLists have a sort property that can
enable you to sort them instantly. You can also associate an object with each
string in the TStringList using the InsertObject method. Given
the presence of all this flexibility in one simple-to-use object, it does not
really make sense to use any other tool for handling lists of strings. It is
definitely worth taking time to explore this object and to see what it can do
for you. New Compiler
Switches I will
spend little time in this book talking about how to pass switches to the
compiler. That subject simply is of no interest to programmers whose primary
goal is rapid application development. However, even under ideal conditions,
there may be times when a programmer may have to take off their RAD hat and
put on their Systems programming hat. So, if you absolutely need to start
tweaking the compiler, here are some new BCC32.EXE switches culled from the online help: New BCC32.EXE switches:
New BCC32.EXE pragmas:
New
switches for the Pascal DCC32.EXE compiler:
Tips On Using
the Visual Tools One of
the biggest problems with BCB forms is getting them to look correct in all
the possible resolutions. There is probably no surefire method for achieving
these ends. However, there are some tips that I have found that help. When I
build forms I almost always set the AutoScroll
property to False. If you do not do this, your form
will always stay the same size, which means that the edges of your form will
be obscured in some resolutions. To avoid this, you would have to resize the form
to fit around the edges of your controls when you changed resolutions. Whatever
approach you take, you simply have to view your form in at least three or
four different modes before releasing it to the public. In particular, be
sure to switch back and forth between Large Fonts and Small Fonts when you
change resolutions. If you are a complete perfectionist, you may decide that
the only solution is to have two sets of forms, one for use with Small Fonts,
and the other for use with Large Fonts. Some
people have a definite knack for designing good-looking forms. If you find
someone who can do this, ask them to critique your forms or even to help you
design them. If you find forms that you like, study them. Use whatever tricks
you can find to help you create decent-looking forms. Try to leave some
whitespace on your forms if you can, and make sure controls line up
vertically and horizontally whenever possible. Summary In this
chapter you saw an overview of all the key areas in which BCB and the VCL
meet. This is a complicated, yet extremely important aspect of the product. I'm sure
it tried your patience at times to have to go through so much dry material.
However, if you have this subject firmly under your belt, you are ready to
begin a serious study of BCB. You now know a good deal about how this product
is put together, and why it was constructed in this particular fashion. This
is the kind of knowledge for which there is no substitute. You simply have to
know these kinds of things about a product before you can take advantage of
its best features. BCB is a
tool for using components and objects to make applications. Any type of
application you can conceive of creating can be made better in Borland
C++Builder than it can with any other C/C++ compiler. What do I mean by
better? I mean that it can be put together faster, with a better chance of
having the right design, and with fewer bugs. In order to get that power, BCB
had to tap into the VCL. In this chapter you saw how it was done. One of
the most important themes of this chapter was the central role played by
components, properties, and events in the BCB programming model. This product
does a lot of great things, but the key features that make it all possible
are the support for components, properties, and events. Much of the
groundwork for that support was laid out in this chapter. VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++
Builder.
|