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.
Object Pascal originally had no support for functions that took a variable array of parameters. As the spec for Delphi developed, it became clear that there had to be a solution to this problem. The language needed to support functions that took a varying array of parameters because various database calls demanded this functionality. In particular, routines that dealt with keyed fields needed varying numbers of parameters depending on the number of fields present in a composite key.

An
array of const was the solution the Object Pascal team came up with for this problem. On the Delphi side, this is a elegant solution that enables you to write simple, easy-to-read code. However, this is not the same solution that the C and C++ languages used when resolving a similar problem. As a result, arrays of const are implemented in an entirely different manner than either the variadic arguments such as you find in sprintf, or the standard C++ practice of function overloading. There was, in short, no common ground between the two languages when it came to this subject.

Hence you find your self wandering down this dark alley, where
OpenArrays meet the array of const. I won't make myself look foolish by trying to claim that the solution found here is elegant, but it is usable. Given the nature of the problem the BCB team had thrust upon them, the solution seems reasonable to me.
I also do not feel that this particular low-rent district is typical of BCB neighborhoods. I love BCB components, properties, and events. These are areas where the tool really shines. Open arrays, on the other hand, are at best nothing more than a satisfactory solution to a difficult problem.

One important point to note about the BCB environment, however, is that you can use function overloading in some cases,
OPENARRAYS in other cases, or switch to Object Pascal code and use elegant and concise solution provided by an array of const. This environment provides a rich set of tools from which you can pick and choose. Because you have access to the tools available in two different languages, there is no other programming environment on the market that even comes close to the richness of features found in BCB.


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.

For instance, the following two lines have the same effect on your code:

 

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.

I try to keep some order in my code by always using
typedef to declare types, while I generally use #define only to declare constants. No one is perfect, and I probably break that rule from time to time, but I try to follow it with some regularity.

A
typedef has effect only within a particular scope. I generally declare types in the header for a unit, unless I want to limit the scope of the type to a particular method or function, in which case I declare the type inside that method or function.

I will not go into this subject any further in this book, nor is the discussion included here meant to be exhaustive. If you want additional information on the
typedef keyword, you should turn to a primer on C++. For this book all you need to know is that typedefs provide a way of declaring your own types within a particular scope. I never use it for any other purpose, and I try never to use any other technique for declaring types.


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 Delphi on a page called Samples, but are not installed by default in BCB. If you go into the Examples\Controls directory, you will find an explanation of how to install these components.

In particular, the spin edit control can be a useful tool to use in this kind of program. Check the CD that accompanies this book for an update on this issue and how it affects these programs.


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);

 

}

NumberSet works with a simple set containing the numbers between 0 and 9:

 

 

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(Union, 3);

 

}

 

 

 

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);

 

}

 

//--------------------------------------------------------------------------

The SetBasics program uses a set of
char instead of a set of integers:

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.

2.
In the Title field, give your new menu item a name, such as
SysDefs.

3.
In the Program field, enter the name of the executable you want to run, such as Notepad, Visual SlickEdit, CodeWright, or MultiEdit.

4.
Set up a working directory, which is usually the place where the include files are located:
g:\bcb\include\vcl.

5.
Pass in the name of the file you want to launch in the Parameters field.

6.
Click the Close button.

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 Delphi because a variant in Object Pascal takes on some of the attributes of an integer after being assigned to that type. The Variant class implemented in SYSDEFS.H, however, allows this kind of behavior and handles it properly.

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.

If I were writing the documentation for the VCL, I might decide not to show you the specific record shown previously because these kinds of details might change in later versions of the product, just as the internal representation for long strings might change. However, in this book I do not claim to be defining the language in a set of official documents. Instead, I am explaining the product to you, using the techniques that seem most useful. In this particular case, I think it helps to see behind the scenes and into the particulars of this implementation of the Variant type. Whether or not this will become a documented fact or an undocumented trick is not clear at the time of this writing.


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);

 

}

A typical run of this program is shown in Figure 3.6.

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:

  • -Vx switch for truly empty (0 length) structs
  • -He switch (extern types in .OBJs)
  • -Hs switch (smart-cached headers)

New BCC32.EXE pragmas:

  • #pragma link "obj" (object file dependencies)
  • #pragma resource "res" (resource file dependencies)
  • #pragma anon_struct on (for support of VCL variant records)

New switches for the Pascal DCC32.EXE compiler:

  • -jp switch: creates Borland C++ compatible .OBJ files
  • -jph switch: creates C++Builder compatible header (.HPP) files from Object Pascal unit files (.DCL)  
  • -jphn switch: uses the Object Pascal unit name as the enclosing C++ namespace for both .OBJs and .HPPs that are generated
  • -n switch: specify .DCU output directory  
  • -nh switch: specify .HPP output directory
  • -no switch: specify .OBJ output directory

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.

 

<<Back (Part 02)  

 

VMS Desenvolvimentos

Diversas Dicas, Apostilas, Arquivos Fontes, Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++ Builder.