Creating Descendants of Existing Components - Part 01

 

Overview

This chapter and the next two cover building components. Component development is one of the most important technologies in contemporary programming, and no C++ environment on the market makes them easier to use or to create than BCB.

You build three types of components in this chapter:

  • Descendants that change default settings in existing components.

 

  • Descendants that add features to existing components.

 

  • Tools built on top of abstract component base classes such as TWinControl, TCustomControl, TComponent, and TGraphicControl. You make descendants from these classes if you want to build your own components from the bottom up.

This chapter presents material on building visual components, Chapter 23 focuses on building unique components that are polymorphic, and Chapter 24 explores building nonvisual components. Nonvisual components descend directly from TComponent and appear on the form only at design time.

More specifically, the components built in this chapter fall into two categories:

  • The first group is a set of TEdit, TLabel, and TPanel descendants that show how to change default colors, captions, and fonts. This section of the chapter also covers building components that consist of several different child components; that is, this section shows how to group components together to form new components. The specific example included with this book shows a panel that comes with two radio buttons.

 

  • The second tool is a clock component that can be dropped on a form, and stopped and started at will.

In Chapter 24, you will see a nonvisual control that knows how to iterate through subdirectories. You can use it to build programs that search for files, delete all files with a certain extension, and so on.

Besides components, this chapter also briefly covers two related topics: Property editors are used to edit the properties of components. The classic examples are the common dialogs that pop up when you edit the Color or Font properties that belong to most visible components. The drop-down lists and string-editing capabilities found in the Object Inspector are also property editors.

Component editors are associated not with a single property, but with an entire component. An example is the Fields Editor used with the TTable and TQuery components.

The property editors and component editors are related to a broader topic called the Tools API. The Tools API consists of a series of interfaces to the BCB IDE that allows you to build experts, interfaces to version control systems, and similar utilities. The API for property editors and component editors and the Tools API are defined in files with names that end in INTF, which stands for "Interface." For example, the TPropertyEditor class is found in DSGNINTF.HPP.

Components, properties, and events, along with their associated component editors and property editors are perhaps the most important topics in BCB programming. In many ways, this chapter and the next two are the keystones around which this entire book has been designed.

The source for all the components created in this chapter is found in the Chap22 and Utils directories on the CD that accompanies this book. The source on the CD compiles correctly, and all of these components work as described in this chapter. Because of the default behavior of the C++ linker, you might have to run a program called BuildObjs before installing some of these components. This program is in the Utils directory. A description of this program is in the readme file on the CD. If you have any trouble with the code in this chapter, turn to the readme!

Component Theory

BCB components have three outstanding strengths:

  • They are native components, built in BCB's language, without recourse to a complicated API such as OLE. You therefore can write, debug, and test your components using the same rules you use in standard BCB programs. You don't need to learn a whole new programming model, as you do when you write ActiveX controls. In short, writing a BCB component is about 10 times easier than writing an OCX or VBX component.

 

  • They are fully object-oriented, which means you can easily change or enhance existing components by creating descendant objects.

 

  • They are small, fast, and light, and can be linked directly into your executables. Native BCB components are orders of magnitude smaller than most ActiveX controls, and they link directly into your programs. In particular, a native BCB component is just a particular kind of object, so it links into your programs naturally, just as any other object would, without any handwaving.

Few people would claim that VBXs aren't groundbreaking, that ActiveX controls aren't going to be very important, or that OLE2 is not an enormously promising architecture. However, BCB components are relatively easy to create and come in a light, easy-to-use package. You can create BCB components that do nearly anything, from serial communications to database links to multimedia. These capabilities give BCB a big advantage over other tools that force you to use large, complex, and unwieldy component models.


NOTE: Most publicly available components cost in the range of $50 to $150. Many of these tools encapsulate functionality that might cost tens of thousands of dollars to produce in-house. For example, a good communication library might take a year to build. However, if a company can sell the library in volume, it can afford to charge $100 or $200 for the same product. That's a real bargain. And most of these tools are easy to use. Building components is a great way for relatively small third-party companies to make money, and buying components is a great way to save time on big projects. These ground-breaking tools are changing everything about the way programs are constructed.



BCB components are flexible tools easily built by anyone who knows OOP and the BCB language. In this package, you have explanations detailing all the prerequisite knowledge that component builders need, from a description of BCB itself, through a description of its language, and on to an overview of its implementation of OOP. From this foundation, you can easily begin building your own components.

Creating Descendants of an Existing Component

In this section, you will see how to create a series of custom TEdit, TPanel, and TLabel controls. The changes made to the standard TEdit and TLabel components involve tweaking their colors, as well as their fonts' colors, names, sizes, and styles. The goal is to show how to create a suite of custom controls that you can place on the Component Palette and use for special effects, or to define the look and feel of a certain set of applications belonging to a particular department or company.

With projects like this, starting with one simple example is best, and then you can move on to more complex components. Listings 22.1 through 22.3 contain a sample component and a program that allows you to test the new component before you place it on the Component Palette. The component is stored in a module called Unleash1. The Unleash1 source code is the first version of a unit that will be expanded later in the chapter. Scan through it and check out its basic structure. Once it is clear to you how to create and test the component, I will briefly discuss how to place it on the Component Palette.

Listing 22.1. The header file for a simple component descending from TEdit.

 
 
///////////////////////////////////////
 
// Unleash1.h
 
// Simple example of creating a component
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#ifndef Unleash1H
 
#define Unleash1H
 
//--------------------------------------------------------------------------
 
#include <vcl\sysutils.hpp>
 
#include <vcl\controls.hpp>
 
#include <vcl\classes.hpp>
 
#include <vcl\forms.hpp>
 
#include <vcl\StdCtrls.hpp>
 
//--------------------------------------------------------------------------
 
class TSmallEdit : public TEdit
 
{
 
private:
 
protected:
 
public:
 
  virtual __fastcall TSmallEdit(TComponent* Owner);
 
__published:
 
};
 
//--------------------------------------------------------------------------
 

#endif

Listing 22.2. The code for a simple component descending from TEdit.

 
 
///////////////////////////////////////
 
// Unleash1.cpp
 
// Simple example of creating a component
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
#include "Unleash1.h"
 
//--------------------------------------------------------------------------
 
static inline TSmallEdit *ValidCtrCheck()
 
{
 
  return new TSmallEdit(NULL);
 
}
 
//--------------------------------------------------------------------------
 
__fastcall TSmallEdit::TSmallEdit(TComponent* Owner)
 
  : TEdit(Owner)
 
{
 
  Color = clBlue;
 
  Font->Color = clYellow;
 
  Font->Name = "Times New Roman";
 
  Font->Size = 12;
 
  Font->Style = TFontStyles() << fsBold << fsItalic;
 
}
 
//--------------------------------------------------------------------------
 
namespace Unleash1
 
{
 
  void __fastcall Register()
 
  {
 
    TComponentClass classes[1] = {__classid(TSmallEdit)};
 
    RegisterComponents("Unleash", classes, 0);
 
  }
 
}
 

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

Listing 22.3. The main form for the TestUnleash1 program serves as a test bed for the Unleash1 unit.

 
 
///////////////////////////////////////
 
// Main.cpp
 
// Simple example of creating a component
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
#include "Main.h"
 
#include "Unleash1.h"
 
#pragma link "unleash1"
 
#pragma resource "*.dfm"
 
TForm1 *Form1;
 
__fastcall TForm1::TForm1(TComponent* Owner)
 
  : TForm(Owner)
 
{
 
}
 
void __fastcall TForm1::Button1Click(TObject *Sender)
 
{
 
  TSmallEdit *MyEdit = new TSmallEdit(this);
 
  MyEdit->Parent = this;
 
  MyEdit->Show();
 

}

This program is designed to test the
TSmallEdit control before you add it to the Component Palette. The test program has a single button that responds to clicks. If you click the program's button, the new component appears at the top left of the form, as shown in Figure 22.1. Once again, the goal of the program is to make sure the new edit control is meeting your expectations before trying to compile it as a component.

Figure 22.1.At runtime, the TestUnleash1 main form features a TButton and a custom Edit control called TSmallEdit.

You can easily create this unit, test it, and compile it as a component that's merged in with the rest of the tools on the Component Palette. To start creating the component, choose File | New and select Component from the first page of the Object Repository. The dialog shown in Figure 22.2 then appears.

Figure 22.2.The Component Wizard dialog.

The Component Wizard is a simple code generator, of the type that any reader of this book who has made it this far should be able to write in an hour or two. It simply asks you for the name of the component you want to create and to then select its parent from a drop-down list. After you define the type of tool you want to create, you can select the page in the Component Palette where you want it to reside. You should fill in the blanks with the following information:

Class Name: TSmallEdit
 
Ancestor type: TEdit
 
Palette Page: Unleash
 

For your efforts, the Component Expert churns out the code in Listings 22.4 and 22.5, in which everything is boilerplate except for the first line of the class declaration, the constructor, and the parameters passed to the RegisterComponents method.

Listing 22.4. The header produced by the standard boilerplate output of the Component Expert.

 
 
//--------------------------------------------------------------------------
 
#ifndef Unleash1H
 
#define Unleash1H
 
//--------------------------------------------------------------------------
 
#include <vcl\sysutils.hpp>
 
#include <vcl\controls.hpp>
 
#include <vcl\classes.hpp>
 
#include <vcl\forms.hpp>
 
#include <vcl\StdCtrls.hpp>
 
class TSmallEdit : public TEdit
 
{
 
private:
 
protected:
 
public:
 
  virtual __fastcall TSmallEdit(TComponent* Owner);
 
__published:
 
};
 

#endif

Listing 22.5. The implementation of TSmallEdit as produced by the standard boilerplate output of the Component Expert.

 
 
//--------------------------------------------------------------------------
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
#include "Unleash1.h"
 
//--------------------------------------------------------------------------
 
static inline TSmallEdit *ValidCtrCheck()
 
{
 
  return new TSmallEdit(NULL);
 
}
 
//--------------------------------------------------------------------------
 
__fastcall TSmallEdit::TSmallEdit(TComponent* Owner)
 
  : TEdit(Owner)
 
{
 
}
 
//--------------------------------------------------------------------------
 
namespace Unleash1
 
{
 
  void __fastcall Register()
 
  {
 
    TComponentClass classes[1] = {__classid(TSmallEdit)};
 
    RegisterComponents("Unleash", classes, 0);
 
  }
 
}
 

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

The Component Expert starts by giving you
#include directives designed to cover most of the bases you are likely to touch in a standard component:

 
 
#include <vcl\sysutils.hpp>
 
#include <vcl\controls.hpp>
 
#include <vcl\classes.hpp>
 
#include <vcl\forms.hpp>
 
#include <vcl\StdCtrls.hpp>
 

The next step is to give you a basic class declaration, in which the name and parent are filled in with the choices you specified in the Component Expert dialog. All this business about the scoping directives is just for your convenience, and you can delete any portion of it that you don't think you'll need.

class TSmallEdit : public TEdit
 
{
 
private:
 
  // Private declarations
 
protected:
 
  // Protected declarations
 
public:
 
  // Public declarations
 
  virtual __fastcall TSmallEdit(TComponent* Owner);
 
__published:
 
  // Published declarations
 
};
 

Before you can place a component on the Component Palette, you first must register it with the system:

void __fastcall Register()
 
{
 
  TComponentClass classes[1] = {__classid(TSmallEdit)};
 
  RegisterComponents("Unleash", classes, 0);
 
}
 

Registering a class makes it known to the BCB Component Palette when the unit is compiled into the BCB component library. The Register procedure has no impact on programs compiled with this unit. Unless your program calls the Register procedure (which it should never do), the code for the Register procedure will never execute.

This method first creates an array consisting of the types of components you want to register. Recall that __classid is one of the new pieces of syntax added to the language. It is dis- cussed in Chapter 2, "Basic Facts About C++Builder." After creating the array, you call RegisterComponents, which takes the name of the page you want to use in the Component Palette in the first parameter, the array of metaclasses in the second parameter, and the size of the array in the third parameter. If the page does not exist, it will be created automatically. Here is the declaration for RegisterComponents:

extern void __fastcall RegisterComponents(const System::AnsiString Page,
 
  System::TMetaClass* const * ComponentClasses, const int ComponentClasses_Size);
 

Recall that TMetaClass is declared in SysDefs.h and that it represents a subset of TObject. You can get the metaclass of a VCL object by calling the object's ClassType method.

If you look back at the source for the whole project, you will see that the Register method is placed in its own namespace. This happens because multiple declarations for methods with that name will occur throughout your projects and, indeed, inside BCB itself. As a result, the compiler's need for order must be satiated with a few discreetly placed namespaces. This whole subject will come up again in just a moment, when I talk about recompiling CmpLib32.ccl.

After using the Component Expert, you should save the project. Proceed as you normally would by creating a directory for the project and saving Main.pas and TestUnleash1.cpp inside it. You should not save the new unit that you created into the same directory, however, but you should place it in the Utils directory where you store files such as CodeBox. This code is now going to come into play as part of your system, and as such you want a single path that leads to all related files of this type. If you have all your components in different subdirectories, you will end up with a source path that is long and unwieldy.


NOTE: ValidCtrCheck() verifies that creating an instance of the object at compile time is possible. If you forget to override a pure virtual function, the compilation of the component will fail because the statement "new foo(0)" is now invalid. You can test this by creating a component derived from TCustomGrid (an abstract class) and installing it without doing any work to change the object.

Because
ValidCtrCheck is inline and never called, no code will be generated--so there is no runtime cost.



The goal of this project is to give a component of type
TEdit a new set of default behaviors so that it starts out with certain colors and certain fonts. To do so, you need to override the Create method and change the fonts inside it. The method declaration itself appears automatically:

class TSmallEdit : public TEdit
 
{
 
public:
 
  virtual __fastcall TSmallEdit(TComponent* Owner);
 
};
 

Notice that in the preceding declaration I have removed the private, __published, and protected directives created by the Component Expert. Making this change is neither here nor there; I do it just to keep the amount of code you need to look at as small as possible.

The Create method for TSmallEdit is declared as public. If you think about the process of creating a component dynamically, you will see that the Create method has to be public. This is one method that must be exposed. Like all VCL constructors, TSmallEdit is declared virtual and __fastcall.

Create is passed a single parameter of type TComponent, which is a base class that encapsulates the minimum functionality needed to be an owner of another component and to place a component on the Component Palette. In particular, whatever form you place a component on usually will be the owner of that component.

The implementation of the Create method is simple:

__fastcall TSmallEdit::TSmallEdit(TComponent* Owner)
 
  : TEdit(Owner)
 
{
 
  Color = clBlue;
 
  Font->Color = clYellow;
 
  Font->Name = "Times New Roman";
 
  Font->Size = 12;
 
  Font->Style = TFontStyles() << fsBold << fsItalic;
 
}
 

The code first calls Create, passing in the variable AOwner. As I stated previously, the owner of a component will often, though not always, be the form on which the component is to be displayed. In other words, the user will drop the component onto a form, and that form will become the owner of the component. In such a case, AOwner is a variable that points to the form. The VCL uses it to initialize the Owner property, which is one of the fields of all components.

The next step is to define the color and font that you want to use, with Font->Style defined as follows:

enum TFontStyle { fsBold, fsItalic, fsUnderline, fsStrikeOut };
 
typedef Set<TFontStyle, fsBold, fsStrikeOut>  TFontStyles;
 

NOTE: Don't let all this gobbledygook confuse you! All that occurs in this declaration is that a simple enum type is declared, and then you see a Set declaration for the type that ranges from a low value of fsBold to a high value of fsStrikeOut. Piece of cake!



If you want to add the underline and bold style to the text in the edit control, write the following:

Font->Style = TFontStyles << fsBold << fsUnderline;
 

If you then want to add the italic style at runtime, write this:

Font->Style = Font->Style << fsItalic;
 

To remove the style, write this line:

Font->Style = Font->Style >> fsItalic;
 

At this stage, the code is ready to go on the Component Palette. However, most of the time when you write components, you should test them first to see whether they work.

Even on a fast machine, the process of recompiling CmpLib32.ccl and adding your component to the Component Palette takes between 10 seconds and 2 minutes. Perhaps I'm a bit impatient, but that's a little too long a wait for me if all I need to tweak is a few aspects of my code. As a result, I test things first in a small program and then add the component to the IDE.

To test the new class, drop a button on the program's main form, and create an OnClick handler:

void __fastcall TForm1::Button1Click(TObject *Sender)
 
{
 
  TSmallEdit *MyEdit = new TSmallEdit(this);
 
  MyEdit->Parent = this;
 
  MyEdit->Show();
 
}
 

Don't forget to use View | Project Manager and add the unit to the project. This ensures that the USEUNIT macro is placed in your project source.

This code creates the component and shows it on the main form. this, of course, is the way that TForm1 refers to itself from inside one of its own methods. The owner of the new component is Form1, which will be responsible for disposing of the component when finished with it. This process happens automatically. You never need to worry about disposing of a visible component shown on a form.

The parent of the component is also Form1. The Parent variable is used by Windows when it is trying to decide how to display the form on the screen. If you place a panel on a form and drop a button on the panel, the owner of that button is the form, but the parent is the panel. Ownership determines when and how the component is deallocated, and parental relationships determine where and how the component is displayed. Ownership is fundamentally a BCB issue, whereas parental relationships are primarily concerns of Windows.


NOTE: The next paragraph in this section explains how to recompile CmpLib32.ccl. Remember that you should always keep a backup copy of CmpLib32.ccl on your hard drive. CmpLib32.ccl is stored in the ..\CBUILDER\BIN directory. Beneath this directory, I have created another directory called BACK where I keep a backup copy of CmpLib32.ccl. If worse comes to worst, you can always copy the version of CmpLib32.ccl on your installation CD back into the BIN subdirectory.

My personal experience is that everyone, sooner or later, makes a tasty Mulligan's Stew out of a copy of
CmpLib32.ccl. The worst-case scenario is that you're on the road when this mess happens, and that you don't have access to your installation disk. Don't let this situation happen to you. Keep a backup copy of CmpLib32.ccl on your hard drive at all times!
?FIGURE 17.16. Creating SQL selection criteria in ReportSmith. CCL stands for C++Builder Component Library.



After you run the program and test the component, the next step is to put it up on the Component Palette. To do so, choose Components | Install and then click the Add button. Browse through the directories until you find
Unleash1.Cpp, select OK, and then close the Install Components dialog by clicking OK. At this point, a project called CmpLib32.cpp is compiled. This project creates a huge DLL called CmpLib32.ccl, which contains all the components in the Component Palette, all the component and property editors associated with those components, the form designer part of the IDE, the experts, and other support modules.

After CmpLib32.ccl finishes recompiling, you can start a new project, turn to the newly created Unleash page, and drop your new component onto a form. It will have a blue background, default to Times New Roman, and have its font style set to bold and its font color to yellow. Notice that all the properties of TEdit have been inherited by TSmallEdit. That's OOP in action. You can see an example of the form that uses this control in Figure 22.3.

Figure 22.3.Using the TSmallEdit control on a standard VCL form.

Understanding the Project Source for CmpLib32

You can save the CmpLib32.cpp file used during compilation of CmpLib32.ccl to disk if you choose Options | Environment | Library | Save Library Source Code. After choosing this option and recompiling CmpLib32.ccl, you can go to the \CBUILDER\BIN directory and view your copy of CmpLib32.cpp.

Here is an example of the source code produced by the IDE for use when compiling CmpLib32.ccl:

 
 
//--------------------------------------------------------------------------
 
// Component Palette
 
// Copyright (c) 1996, 1996 by Borland International, All Rights Reserved
 
//
 
// Module generated by C++Builder to rebuild the Component
 
// Palette Library (CmpLib32.ccl).
 
//--------------------------------------------------------------------------
 
//--------------------------------------------------------------------------
 
// In order to be totally Delphi compatible, the Component palette must be
 
// built with a namespaced version of the VCL library. Using namespace allows
 
// us to have multiple functions all called `Register'.
 
//--------------------------------------------------------------------------
 
#if !defined(BCB_NAMESPACES)
 
#define BCB_NAMESPACES
 
#endif
 
//--------------------------------------------------------------------------
 
// Include DSTRING.H - Defines AnsiString support class
 
//--------------------------------------------------------------------------
 
#include <vcl\dstring.h>
 
//--------------------------------------------------------------------------
 
// The following are expanded inline to avoid pulling in the headers
 
// and lengthen the compilation time when rebuilding the palette.
 
//--------------------------------------------------------------------------
 
typedef void* HINSTANCE;
 
#if !defined(WINAPI)
 
#define WINAPI __stdcall
 
#endif
 
#if !defined(DLL_PROCESS_ATTACH)
 
#define DLL_PROCESS_ATTACH 1
 
#endif
 
#if !defined(DLL_THREAD_ATTACH)
 
#define DLL_THREAD_ATTACH  2
 
#endif
 
#if !defined(DLL_THREAD_DETACH)
 
#define DLL_THREAD_DETACH  3
 
#endif
 
#if !defined(DLL_PROCESS_DETACH)
 
#define DLL_PROCESS_DETACH 0
 
#endif
 
namespace Libmain {
 
typedef void __fastcall (*TRegisterProc)(void);
 
extern  void __fastcall RegisterModule(const System::AnsiString Name, 
 
  TRegisterProc RegisterProc);
 
}
 
using namespace Libmain;
 
namespace System {
 
extern void __cdecl ProcessAttachTLS(void);
 
extern void __cdecl ProcessDetachTLS(void);
 
extern void __cdecl ThreadAttachTLS(void);
 
extern void __cdecl ThreadDetachTLS(void);
 
}
 
using namespace System;
 
//--------------------------------------------------------------------------
 
// Prototype for each component's `Register routine. Followed
 
// by instruction to have linker pull in the OBJ. module which
 
// implements the Register routine.
 
//
 
//  Each component is expected to provide a routine with the
 
//  following signature:
 
//
 
//       extern void __fastcall Register(void);
 
//
 
//  This routine must be in a namespace which matches the
 
//  name of the component itself. Therefore, the routine is
 
//  actually prototyped as:
 
//
 
//       namespace Componentname {
 
//           extern void __fastcall Register(void);
 
//       };
 
//
 
//  Note The namespace must be in all lowercase characters
 
//        except for the first one. i.e. Namespacename.
 
//--------------------------------------------------------------------------
 
namespace Stdreg { extern void __fastcall Register(void); }
 
namespace Dbreg { extern void __fastcall Register(void); }
 
namespace Isp { extern void __fastcall Register(void); }
 
namespace Sysreg { extern void __fastcall Register(void); }
 
namespace Quickrep { extern void __fastcall Register(void); }
 
namespace Ocxreg { extern void __fastcall Register(void); }
 
namespace Olereg { extern void __fastcall Register(void); }
 
namespace Ddereg { extern void __fastcall Register(void); }
 
namespace Chartfx { extern void __fastcall Register(void); }
 
namespace Vcfimprs { extern void __fastcall Register(void); }
 
namespace Vcfrmla1 { extern void __fastcall Register(void); }
 
namespace Vcspell { extern void __fastcall Register(void); }
 
namespace Graphsvr { extern void __fastcall Register(void); }
 
namespace Ibreg { extern void __fastcall Register(void); }
 
namespace Win31reg { extern void __fastcall Register(void); }
 
namespace Sampreg { extern void __fastcall Register(void); }
 
namespace Unleash1 { extern void __fastcall Register(void); }
 
// (Search Path for Components (.CPP, .PAS, .OBJ & .LIB)
 
//  => g:\bcb\LIB;g:\bcb\LIB\OBJ;g:\srcc\punleash\utils
 
//
 
// Added to search paths: g:\bcb\LIB
 
// Added to search paths: g:\bcb\LIB\OBJ
 
// Added to search paths: g:\srcc\punleash\utils
 
// Added to project: g:\bcb\bin\cmplib32.cpp FileType: SRC
 
// Added to project: bcbmm.lib FileType: LIB
 
#pragma resource   "StdReg.dcr"  // Link dcr of standard module "StdReg"
 
// Added to project: g:\bcb\LIB\OBJ\DBReg.obj (From SearchPath) FileType: OBJ
 
// Added to project: g:\bcb\LIB\OBJ\DBReg.dcr (From SearchPath) FileType: RES
 
#pragma resource      "ISP.dcr"  // Link dcr of standard module "ISP"
 
#pragma resource   "SysReg.dcr"  // Link dcr of standard module "SysReg"
 
#pragma resource "Quickrep.dcr"  // Link dcr of standard module "Quickrep"
 
#pragma resource   "OLEReg.dcr"  // Link dcr of standard module "OLEReg"
 
#pragma resource   "DDEReg.dcr"  // Link dcr of standard module "DDEReg"
 
#pragma resource  "ChartFX.dcr"  // Link dcr of standard module "ChartFX"
 
#pragma resource "VCFImprs.dcr"  // Link dcr of standard module "VCFImprs"
 
#pragma resource "VCFrmla1.dcr"  // Link dcr of standard module "VCFrmla1"
 
#pragma resource  "VCSpell.dcr"  // Link dcr of standard module "VCSpell"
 
#pragma resource "GraphSvr.dcr"  // Link dcr of standard module "GraphSvr"
 
#pragma resource    "IBReg.dcr"  // Link dcr of standard module "IBReg"
 
#pragma resource "Win31Reg.dcr"  // Link dcr of standard module "Win31Reg"
 
#pragma resource  "SampReg.dcr"  // Link dcr of standard module "SampReg"
 
// Added to project: g:\srcc\punleash\utils\Unleash1.cpp 
 
//  (From SearchPath) FileType: SRC
 
//--------------------------------------------------------------------------
 
// Routine which registers the various modules implementing components
 
//--------------------------------------------------------------------------
 
bool
 
InitCmpLib()
 
{
 
  RegisterModule("StdReg", Stdreg::Register);
 
  RegisterModule("DBReg", Dbreg::Register);
 
  RegisterModule("ISP", Isp::Register);
 
  RegisterModule("SysReg", Sysreg::Register);
 
  RegisterModule("Quickrep", Quickrep::Register);
 
  RegisterModule("OCXReg", Ocxreg::Register);
 
  RegisterModule("OLEReg", Olereg::Register);
 
  RegisterModule("DDEReg", Ddereg::Register);
 
  RegisterModule("ChartFX", Chartfx::Register);
 
  RegisterModule("VCFImprs", Vcfimprs::Register);
 
  RegisterModule("VCFrmla1", Vcfrmla1::Register);
 
  RegisterModule("VCSpell", Vcspell::Register);
 
  RegisterModule("GraphSvr", Graphsvr::Register);
 
  RegisterModule("IBReg", Ibreg::Register);
 
  RegisterModule("Win31Reg", Win31reg::Register);
 
  RegisterModule("SampReg", Sampreg::Register);
 
  RegisterModule("Unleash1", Unleash1::Register);
 
  return true;
 
}
 
//--------------------------------------------------------------------------
 
// Library's entry point
 
//--------------------------------------------------------------------------
 
extern "C"
 
int WINAPI
 
DllEntryPoint(HINSTANCE /*hInstance*/, unsigned long reason, void*)
 
{
 
  switch (reason) {
 
    case DLL_PROCESS_ATTACH:
 
       ProcessAttachTLS();
 
       InitCmpLib();
 
       break;
 
    case DLL_PROCESS_DETACH:
 
       ProcessDetachTLS();
 
       break;
 
    case DLL_THREAD_ATTACH:
 
       ThreadAttachTLS();
 
       break;
 
    case DLL_THREAD_DETACH:
 
       ThreadDetachTLS();
 
       break;
 
  }
 
  return 1;
 
 
 
}
 

To understand the unit, start at the bottom, with the DllEntryPoint function. This routine is called when Windows first loads the DLL into memory and each time it is accessed by a BCB thread. ProcessAttachTLS and related routines are all part of the internal system code. This code is none of our business, so we can safely "pay no attention to the man behind the curtain."

InitCmpLib calls the Register method for each of the modules used in the project. If you look at the bottom of the list, you will see where the Register method for Unleash1 is listed. You might think that we don't have enough calls in this section to create the over 100 components found on the Component Palette. The issue here is that some of the Register methods register 20 or 30 different components at one shot. Here, for example, is the Register method from StdReg.pas:

procedure Register;
 
begin
 
  RegisterComponents(LoadStr(srStandard), [TMainMenu, TPopupMenu, TLabel,
 
    TEdit, TMemo, TButton, TCheckBox, TRadioButton, TListBox, TComboBox,
 
    TScrollBar, TGroupBox, TRadioGroup, TPanel]);
 
  RegisterComponents(LoadStr(srAdditional), [TBitBtn, TSpeedButton,
 
    TMaskEdit, TStringGrid, TDrawGrid, TImage, TShape, TBevel,
 
    TScrollBox]);
 
  RegisterComponents(LoadStr(srWin95), [TTabControl, TPageControl,
 
    TTreeView, TListView, TImageList, THeaderControl, TRichEdit,
 
    TStatusBar, TTrackBar, TProgressBar, TUpDown, THotKey]);
 
  RegisterClasses([TTabSheet]);
 
  RegisterNoIcon([TMenuItem]);
 
  RegisterComponentEditor(TMenu, TMenuEditor);
 
  RegisterComponentEditor(TImage, TGraphicEditor);
 
  RegisterComponentEditor(TPageControl, TPageControlEditor);
 
  RegisterComponentEditor(TTabSheet, TPageControlEditor);
 
  RegisterComponentEditor(TImageList, TImageListEditor);
 
  RegisterPropertyEditor(TypeInfo(string), 
 
    TCustomMaskEdit, `EditMask', TMaskProperty);
 
  RegisterPropertyEditor(TypeInfo(string), 
 
    TCustomMaskEdit, `Text', TMaskTextProperty);
 
  RegisterPropertyEditor(TypeInfo(TTabSheet), TPageControl, `ActivePage',
 
    TActivePageProperty);
 
  RegisterPropertyEditor(TypeInfo(TStatusPanels), nil, `', 
 
    TStatusPanelsProperty);
 
  RegisterPropertyEditor(TypeInfo(THeaderSections), nil, `', 
 
    THeaderSectionsProperty);
 
  RegisterPropertyEditor(TypeInfo(TListColumns), nil, `', 
 
    TListColumnsProperty);
 
  RegisterPropertyEditor(TypeInfo(TListItems), nil, `', 
 
    TListItemsProperty);
 
  RegisterPropertyEditor(TypeInfo(TTreeNodes), nil, `', 
 
    TTreeNodesProperty);
 
end;
 

Sorry about bringing Object Pascal code into the book again, but I think it helps to see what is happening behind the scenes in cases like this one. I will talk about RegisterPropertyEditor later in this chapter. Notice that the VCL calls RegisterComponents just as you do. When you recompile Cmplib, you are rebuilding part of BCB itself and simultaneously redefining the way the VCL works.

The DCR files listed in CmpLib32.cpp are a series of standard Windows resources that contain bitmaps that are placed in the Component Palette. The bitmaps representing each component come from here. In the case of TSmallEdit, it descends from TEdit, and so it inherits TEdit's bitmap, unless I create a DCR file for the component. I will create and discuss DCR files later in this chapter and in the next chapter.

The key point to grasp about DCR files is that they are standard Windows resources containing a bitmap. You can build them with the Image Editor that ships with BCB.

Extending the Unleash Unit

In the second version of the Unleash unit, a new edit control is added, along with two labels and two panels. The additional edits and labels show how quickly you can build on an idea or object when you understand where you're headed. One of the panels shows how you can get rid of the annoying label that always shows up in the middle of a panel, and the other shows a preliminary take on how you can create a single component that contains other components. Specifically, it shows how to create a panel that comes already equipped with two radio buttons.

The code for the new version of the Unleash2.cpp unit is shown in Listings 22.6 and 22.7, and its test bed appears in Listing 22.8. Run TestBed first, and make sure the new components are working correctly. If all is well, then add the new components to the Component Palette.

If you have two units on your system, both of which contain instances of TSmallEdit, uninstalling the first instance before trying to install the new instance is probably best. For instance, you may have two files on your system, one called Unleash1.cpp and the second called Unleash2.cpp. Both might contain an instance of TSmallEdit. Under such circumstances, it's best to use Component | Install | Remove to remove the first version of TSmallEdit before replacing it with a second version. A second alternative is just to rename the second version of the component to TSmallEditTwo, which is what I do with the source found on the CD that accompanies this book. If you have only one version of TSmallEdit, and you just want to update it, you don't need to remove the first instance before installing the updated instance.

Listing 22.6. The header for the second version of the Unleash2.cpp unit contains a class that comes equipped with two radio buttons.

 
 
///////////////////////////////////////
 
// Unleash2.h
 
// Simple components
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#ifndef Unleash2H
 
#define Unleash2H
 
//--------------------------------------------------------------------------
 
#include <vcl\sysutils.hpp>
 
#include <vcl\controls.hpp>
 
#include <vcl\classes.hpp>
 
#include <vcl\forms.hpp>
 
#include <vcl\StdCtrls.hpp>
 
//--------------------------------------------------------------------------
 
class TSmallEditTwo : public TEdit
 
{
 
public:
 
  virtual __fastcall TSmallEditTwo(TComponent* Owner);
 
};
 
 
 
class TBigEdit : public TSmallEditTwo
 
{
 
public:
 
  virtual __fastcall TBigEdit(TComponent* Owner)
 
    :TSmallEditTwo(Owner) { Width = 250; Font->Size = 24; }
 
};
 
 
 
class TSmallLabel : public TLabel
 
{
 
public:
 
  virtual __fastcall TSmallLabel(TComponent* Owner);
 
};
 
 
 
class TBigLabel : public TSmallLabel
 
{
 
public:
 
  virtual __fastcall TBigLabel(TComponent *Owner)
 
    :TSmallLabel(Owner) { Font->Size = 24; }
 
};
 
 
 
class TEmptyPanel : public TPanel
 
{
 
protected:
 
  void __fastcall Loaded(void);
 
public:
 
  virtual __fastcall TEmptyPanel(TComponent *Owner);
 
  virtual void __fastcall SetParent(TWinControl *AParent);
 
};
 
 
 
class TRadio2Panel : public TEmptyPanel
 
{
 
private:
 
  TRadioButton *FRadio1;
 
  TRadioButton *FRadio2;
 
public:
 
  virtual __fastcall TRadio2Panel(TComponent *Owner);
 
  __property TRadioButton *Radio1={read=FRadio1};
 
  __property TRadioButton *Radio2={read=FRadio2};
 
};
 
 
 
//--------------------------------------------------------------------------
 

#endif

Listing 22.7. The second version of the Unleash unit, called Unleash2, contains a class that comes equipped with two radio buttons.

 
 
///////////////////////////////////////
 
// Unleash2.cpp
 
// Simple components
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
#include "Unleash2.h"
 
 
 
///////////////////////////////////////
 
// TSmallEditTwo //////////////////////
 
///////////////////////////////////////
 
 
 
static inline TSmallEditTwo *ValidCtrCheck()
 
{
 
  return new TSmallEditTwo(NULL);
 
}
 
 
 
__fastcall TSmallEditTwo::TSmallEditTwo(TComponent* Owner)
 
  : TEdit(Owner)
 
{
 
  Color = clBlue;
 
  Font->Color = clYellow;
 
  Font->Name = "Times New Roman";
 
  Font->Size = 12;
 
  Font->Style = TFontStyles() << fsBold << fsItalic;
 
}
 
 
 
///////////////////////////////////////
 
// TSmallLabel ////////////////////////
 
///////////////////////////////////////
 
 
 
__fastcall TSmallLabel::TSmallLabel(TComponent* Owner)
 
  : TLabel(Owner)
 
{
 
  Color = clBlue;
 
  Font->Color = clYellow;
 
  Font->Name = "Times New Roman";
 
  Font->Size = 12;
 
  Font->Style = TFontStyles() << fsBold;
 
}
 
 
 
///////////////////////////////////////
 
// TEmptyPanel ////////////////////////
 
///////////////////////////////////////
 
 
 
__fastcall TEmptyPanel::TEmptyPanel(TComponent *Owner)
 
   :TPanel(Owner)
 
{
 
}
 
 
 
void __fastcall TEmptyPanel::Loaded(void)
 
{
 
  TPanel::Loaded();
 
  Caption = "";
 
}
 
 
 
void __fastcall TEmptyPanel::SetParent(TWinControl *AParent)
 
{
 
  TPanel::SetParent(AParent);
 
  Caption = "";
 
}
 
 
 
 
 
///////////////////////////////////////
 
// TRadio2Panel ///////////////////////
 
///////////////////////////////////////
 
 
 
__fastcall TRadio2Panel::TRadio2Panel(TComponent *Owner)
 
  :TEmptyPanel(Owner)
 
{
 
  Width = 175;
 
  Height = 60;
 
 
 
  FRadio1 = new TRadioButton(this);
 
  FRadio1->Parent = this;
 
  FRadio1->Caption = "Radio1";
 
  FRadio1->Left = 20;
 
  FRadio1->Top = 10;
 
  FRadio1->Show();
 
 
 
  FRadio2 = new TRadioButton(this);
 
  FRadio2->Parent = this;
 
  FRadio2->Caption = "Radio2";
 
  FRadio2->Left = 20;
 
  FRadio2->Top = 32;
 
  FRadio2->Show();
 
}
 
 
 
namespace Unleash2
 
{
 
  void __fastcall Register()
 
  {
 
    TComponentClass classes[6]={__classid(TSmallEditTwo),
 
      __classid(TBigEdit), __classid(TSmallLabel), __classid(TBigLabel),
 
      __classid(TEmptyPanel), __classid(TRadio2Panel)};
 
    RegisterComponents("Unleash", classes, 5);
 
  }
 
}
 

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

Listing 22.8. The test bed for the Unleash2 unit.

 
 
///////////////////////////////////////
 
// Main.cpp
 
// Simple components: TestUnleash2
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
#include "Main.h"
 
#include "Unleash2.h"
 
#pragma link "Unleash1"
 
#pragma link "Unleash2"
 
#pragma resource "*.dfm"
 
TForm1 *Form1;
 
__fastcall TForm1::TForm1(TComponent* Owner)
 
  : TForm(Owner)
 
{
 
   TComponentClass classes[1]={__classid(TRadioButton)};
 
   RegisterClasses(classes, 0);
 
}
 
void __fastcall TForm1::TestComponentsBtnClick(TObject *Sender)
 
{
 
  TestComponentsBtn->Enabled = False;
 
 
 
  TBigEdit *BigEdit = new TBigEdit(this);
 
  BigEdit->Parent = this;
 
  BigEdit->Show();
 
  TEmptyPanel *E = new TEmptyPanel(this);
 
  E->Parent = this;
 
  E->Top = BigEdit->Height + 5;
 
  E->Show();
 
  TRadio2Panel *P = new TRadio2Panel(this);
 
  P->Parent = this;
 
  P->Top = E->Top + E->Height + 5;
 
  P->Radio1->Caption = "As you from crimes would pardon'd be ";
 
  P->Radio1->Width = Canvas->TextWidth(P->Radio1->Caption) + 20;
 
  P->Radio2->Caption = "Let your indulgence set me free ";
 
  P->Radio2->Width = Canvas->TextWidth(P->Radio2->Caption) + 20;
 
  P->Width = max(P->Radio1->Width, P->Radio2->Width) + P->Radio1->Left + 20;
 
  P->Show();
 

}

You can find this program in the directory called
TestUnleash2 on the CD that accompanies this book. Figure 22.4 shows the program in action.

Figure 22.4.The TestUnleash2 program demonstrates how to test a series of components before placing them on the Component Palette.

After you've created a component that does something you like, you can easily create children of it. Class TBigEdit descends from TSmallEditTwo:

class TBigEdit : public TSmallEditTwo
 
{
 
public:
 
  virtual __fastcall TBigEdit(TComponent* Owner)
 
    :TSmallEditTwo(Owner) { Width = 250; Font->Size = 24; }
 
};
 

It inherits its font nearly unchanged from TSmallEditTwo, except that it sets Font->Size to 24, a hefty figure that helps the control live up to its name. This elegant syntax is a good example of how OOP can save you time and trouble while still allowing you to write clear code.

The label controls shown in this code work in exactly the same way the edit controls do, except that they descend from TLabel rather than from TEdit.

The TEmptyPanel component rectifies one of the petty issues that sometimes annoys me: Every time you put down a panel, it gets a caption. Most of the time, the first thing you do is delete the caption so that you can place other controls on it without creating a mess!

At first, it would appear that you can change the caption of TPanel by overriding its constructor. All you would need to do is set the Caption property to an empty string:

class TEmptyPanel : public TPanel
 
{
 
public:
 
  virtual __fastcall TEmptyPanel(TComponent *Owner)
 
   :TPanel(Owner) { Caption = ""; }
 
};
 

This code should work correctly, but in the first version of C++Builder the caption does not get changed. There are two interesting workarounds for this problem that illustrate interesting facts about the VCL. The first workaround is to override the Loaded method:

void __fastcall TEmptyPanel::Loaded(void)
 
{
 
  TPanel::Loaded();
 
  Caption = "";
 
}
 

The code shown in this overridden Loaded method will change the caption of the component at runtime, but not at design time. The Loaded method is called after a component has been loaded from a stream but before it is shown to the user. The method exists so that you can set the value of properties that rely on the value of other properties.

A second technique involves overriding the SetParent method:

void __fastcall TEmptyPanel::SetParent(TWinControl *AParent)
 
{
 
  TPanel::SetParent(AParent);
 
  Caption = "";
 
}
 

This will fix the problem at both design time and at runtime.

In this example, I am not using SetParent for its intended purpose. To understand the method's real purpose, you need to remember that a component is always shown on the surface of its parent. The primary purpose of the SetParent method is to give you a chance to change the parent of a component just as it is being made visible to the user. This allows you to store a component in a DFM file with one parent, and to change that parent at runtime. It's unlikely you will ever need to change the parent of a component, but there are occasions when it is useful, and so the VCL gives you that ability.

It is obviously useful to know that you can override the SetParent and Loaded methods in order to change properties at two different stages in the process of allocating memory for a component. You should note, however, that in this case it is a bit strange that you have to do so, because merely overriding the constructor should have turned the trick.

The last new component in Unleash2.cpp enables you to drop down a panel that comes equipped with two radio buttons. This way, you can make a single control out of a set of components that are often combined.

You could create other controls that contain three, four, or more radio buttons. Or you could even create a panel that would populate itself with a specific number of radio buttons.

The declaration for this new radio button is fairly simple:

class TRadio2Panel : public TEmptyPanel
 
{
 
private:
 
  TRadioButton *FRadio1;
 
  TRadioButton *FRadio2;
 
public:
 
  virtual __fastcall TRadio2Panel(TComponent *Owner);
 
  __property TRadioButton *Radio1={read=FRadio1 };
 
  __property TRadioButton *Radio2={read=FRadio2 };
 
};
 

The actual radio buttons themselves are declared as private data, and access to them is given by the Radio1 and Radio2 properties.

Write access to the radio button properties is not needed because you can modify without it. The following statement performs one read of RP->Radio1 and one write to the Caption property of that radio button:

P->Radio1->Caption = "Control one";
 

You don't want write access to the properties either because that would allow the user to assign them garbage (or NULL).

The constructor for the Radio2Panel begins by setting the width and height of the panel:

__fastcall TRadio2Panel::TRadio2Panel(TComponent *Owner)
 
  :TEmptyPanel(Owner)
 
{
 
  Width = 175;
 
  Height = 60;
 
  FRadio1 = new TRadioButton(this);
 
  FRadio1->Parent = this;
 
  FRadio1->Caption = "Radio1";
 
  FRadio1->Left = 20;
 
  FRadio1->Top = 10;
 
  FRadio1->Show();
 
  FRadio2 = new TRadioButton(this);
 
  FRadio2->Parent = this;
 
  FRadio2->Caption = "Radio2";
 
  FRadio2->Left = 20;
 
  FRadio2->Top = 32;
 
  FRadio2->Show();
 
}
 

The next step is to create the first radio button. Notice that the code passes this as the owner and sets the parent to the panel itself. The rest of the code in the Create method is too trivial to merit comment.

Under some circumstances, you may need to register TRadioButton:

TComponentClass classes[1]={__classid(TRadioButton)};
 
RegisterClasses(classes, 0);
 

This event would normally occur when you drop a component on a form. However, in this case a TRadioButton is not necessarily ever dropped explicitly on a form. As a result, the safe thing to do is register the component, possibly in the constructor for the class.

When you're ready to test the TRadio2Panel object, you can write the following code in the test-bed program to take it through its paces:

TRadio2Panel *P = new TRadio2Panel(this);
 
P->Parent = this;
 
P->Top = E->Top + E->Height + 5;
 
P->Radio1->Caption = "As you from crimes would pardon'd be ";
 
P->Radio1->Width = Canvas->TextWidth(P->Radio1->Caption) + 20;
 
P->Radio2->Caption = "Let your indulgence set me free ";
 
P->Radio2->Width = Canvas->TextWidth(P->Radio2->Caption) + 20;
 
P->Width = max(P->Radio1->Width, P->Radio2->Width) + P->Radio1->Left + 20;
 
P->Show();
 

Note that each radio button that belongs to the panel acts exactly as you would expect a normal radio button to act, except you have to qualify it differently before you access it. I use the TextWidth property of Canvas to discover the width needed for the string, and then add 20 to take into account the button itself.


NOTE: If you want, you can surface Radio1 and Radio2 in the Object Inspecter as published properties of TRadio2Panel. However, when you first do so, they will have no property editors available because BCB has no built-in property editors for TRadioButtons. To build your own, you can refer to the DsgnIntf.pas unit that ships with BCB, as well as the upcoming discussion of the Clock component and the Tools API.


The following code is used to register the objects in the Unleash2 unit so they can be placed on the Component Palette:

namespace Unleash2
 
{
 
  void __fastcall Register()
 
  {
 
    TComponentClass classes[6]={__classid(TSmallEditTwo),
 
      __classid(TBigEdit), __classid(TSmallLabel), __classid(TBigLabel),
 
      __classid(TEmptyPanel), __classid(TRadio2Panel)};
 
    RegisterComponents("Unleash", classes, 5);
 
  }
 
}
 

Notice how I register multiple components in this example at once by creating an array of type TComponentClass. After registering the objects, you can place them on the Component Palette and use them in a program, as shown in Figures 22.5 and 22.6.

Figure 22.5.The main form of a VCL application that uses some of the components from the Unleash2 unit.

Figure 22.6.The new components on the Component Palette, along with some other controls created in later chapters.


NOTE: To create custom bitmaps for components shown on the Component Palette, you need to create a standard resource with the extension .dcr. The Image Editor component that ships with BCB is designed to handle this chore.

I ship a few DCR files in the
Utils directory on the CD that accompanies this book. You can study them to see how to proceed, or you can find the DCR files for other components that ship with BCB. To look at a DCR file, open it with the Image Editor.

The DCR file should have the name of the unit that contains the controls, and each small 24x24 bitmap that you create in the file should be named after the component to which it belongs. For example, the DCR file for this unit would be called
Unleash2.dcr, and the bitmaps inside it would have names like TBIGEDIT and TEMPTYPANEL.
I will discuss this subject in more depth later in this chapter.


Before closing this section, I'd like to add some notes about how BCB handles streaming chores. The good news is that most of the time you don't have to concern yourself with streaming at all. BCB handles most streaming chores automatically. In particular, it will automatically stream published properties that are simple types. Only under limited circumstances must you explicitly stream the fields of your object.

If a property type is a TComponent or descendant, the streaming system assumes it must create an instance of that type when reading it in. If a property type is TPersistent but not TComponent, the streaming system assumes it is supposed to use the existing instance available through the property and read values into that instance's properties.

The Object Inspector knows to expand the properties of TPersistent but not TComponent descendants. This expansion is not done for TComponent descendants because they are likely to have a lot more properties, which would make navigating the Object Inspector difficult.

Building Components from Scratch

In the previous examples, you created descendants of existing components. Now you're ready to see how to create entirely new components. The main idea to grasp here is that you can make a new component descend from three abstract objects. The term "abstract" can have a specific technical meaning, but here I'm using it to refer to any object that exists only so that you can create descendants of it. In short, the following three objects have built-in functionality that all components need to access, but you would never want to instantiate an instance of any of them:

  • TWinControl and TCustomControl are base classes that can be used to produce a Windows control that can receive input focus and that has a standard Windows handle that can be passed to API calls. TWinControl descendants exist inside their own window. TEdit, TListBox, TTabbedNotebook, TNotebook, and TPanel are all examples of this type of control. Most components of this type actually descend from TCustomControl, which is in turn a descendant of TWinControl. The distinction between the two classes is that TCustomControl has a Paint method, and TWinControl does not. If you want to draw the display of your new component, you should make it inherit from TCustomControl. If the object already knows how to draw itself, inherit from TWinControl.

 

  • TGraphicControl is for components that don't need to receive input focus, don't need to contain other components, and don't need a handle. These controls draw themselves directly on their parent's surface, thereby saving Windows resources. Not having a window handle eliminates a lot of Windows management overhead, and that translates into faster display updates. In short, TGraphicControls exist inside their parent's window. They use their parent's handle and their parent's device context. They still have Handle and Canvas fields that you can access, but they actually belong to their parent. TLabel and TShape objects are examples of this type of component. The drawback with this system is that the component can never get the focus!

 

  • TComponent enables you to create nonvisual components. If you want to make a tool such as the TTable, TQuery, TOpenDialog, or TTimer devices, this is the place to start. You can place these components on the Component Palette, but they perform some internal function that you access through code instead of appearing to the user at runtime. A tool such as TOpenDialog can pop up a dialog, but the component itself remains invisible.

Create a TWinControl or TCustomControl descendant whenever the user needs to interact directly with a visible control. If the user doesn't need to interact with a visible component, create a TGraphicControl descendant.

To get a handle on the issues involved here, you should place a TShape or TLabel control on a form and run the program. Clicking or attempting to type on these controls produces no noticeable result. These components don't ever receive the focus. Now place a TEdit control on the form. It responds to mouse clicks, gets the focus, and you can type in it. TEdit controls are descendants of TWinControl, and TShape is a descendant of TGraphicControl.


NOTE: I should add one caveat to the rules about TGraphicControl explained previously. In one limited sense, the user can interact with a TGraphicControl. For example, these controls do receive mouse messages, and you can set the mouse cursor when the mouse flies over them. They just can't receive keyboard input focus. If an object can't receive focus, it usually seems inert to the user.


If you're having trouble deciding whether you want to make descendants from TWinControl or TCustomControl, you should always go with TCustomControl. It has a real Paint method and some other functionality that is useful when creating a component of your own. If you want to wrap an existing Windows control inside a VCL object, you should start with TWinControl. Most BCB components that follow this path begin by creating intermediate custom objects, so the hierarchy of TEdit looks like this:

TWinControl
 
TCustomEdit
 
TEdit
 

The hierarchy of TListBox looks like this:

TWinControl
 
TCustomListBox
 
TListBox
 

Of course, BCB wraps all the major Windows controls for you, so you won't need to perform this operation unless you're working with a specialized third-party control of some sort.

Following are the declarations for TGraphicControl and TCustomControl, as they appear in Controls.hpp:

class __declspec(delphiclass) TGraphicControl;
 
class __declspec(pascalimplementation) TGraphicControl : public TControl
 
{
 
  typedef TControl inherited;
 
private:
 
  Graphics::TCanvas* FCanvas;
 
  MESSAGE void __fastcall WMPaint(Messages::TWMPaint &Message);
 
protected:
 
  virtual void __fastcall Paint(void);
 
  __property Graphics::TCanvas* Canvas = {read=FCanvas, nodefault};
 
public:
 
  __fastcall virtual TGraphicControl(Classes::TComponent* AOwner);
 
  __fastcall virtual ~TGraphicControl(void);
 
};
 
class __declspec(delphiclass) TCustomControl;
 
class __declspec(pascalimplementation) TCustomControl : public TWinControl
 
{
 
  typedef TWinControl inherited;
 
private:
 
  Graphics::TCanvas* FCanvas;
 
  MESSAGE void __fastcall WMPaint(Messages::TWMPaint &Message);
 
protected:
 
  virtual void __fastcall Paint(void);
 
  virtual void __fastcall PaintWindow(HDC DC);
 
  __property Graphics::TCanvas* Canvas = {read=FCanvas, nodefault};
 
public:
 
  __fastcall virtual TCustomControl(Classes::TComponent* AOwner);
 
  __fastcall virtual ~TCustomControl(void);
 
public:
 
  /* CreateParented */ __fastcall TCustomControl(HWND ParentWindow) :
 
   Controls::TWinControl(ParentWindow) { }
 
};
 

You can see that these objects are fairly simple. If you go back one step further in the hierarchy to TControl or TWinControl, you see huge objects. For example, the declaration for class TWinControl is nearly 200 lines long (not the implementation, mind you, just the type declaration).

I'm showing you this source code because component builders should work directly with the source rather than use the online help or the docs. For simple jobs, you can easily create your own components without the source. However, if you have a big project, you have to get the source code if it did not ship with your product. The INT files that ship with all versions of BCB are very helpful, but you'll find no replacement for the actual source. The source is available with the client/server version of BCB and also as an up-sell from Borland.

The Clock Component

You now know enough to build a component from the ground up. The CLOCK unit, as shown in Figure 22.7, is a simple clock that you can pop onto a form, and activate and deactivate at will. In particular, you can start the clock running and then tell it to stop by changing the value of a Boolean property called Running.

Figure 22.7.The CLOCK unit as it appears on its own test bed, before being placed on the Component Palette.

When you're constructing class TClock, the first thing you need to decide is whether the clock is going to descend from TWinControl or TGraphicControl. If you've built a clock in Windows before, you know that one of the best ways to drive it is with a Windows timer. Timers require the presence of a handle so that they can be stopped and started; furthermore, they send their WM_TIMER messages to the window that owns them. Because a TGraphicControl descendant isn't a real window, it will not automatically get the messages. As a result, TGraphicControl is not an ideal choice for this type of object.

Of course, the objections to using TGraphicControl raised in the preceding paragraph aren't insurmountable. If you really want to, you can still make your clock work this way. However, there's no point in expending effort that isn't strictly necessary, so I have opted for the simplest design possible and have made the class a descendant of TCustomControl. I chose TCustomControl rather than TWinControl because I needed a Paint method in which I could draw the clock. (See Figure 22.8.)

Figure 22.8.The TClock and TColorClock controls shown on a form.

The code, shown in Listings 22.9 through 22.11, also contains a special property editor, as well as a simple component editor. As you will see, neither of these tools is inherently difficult to build.


NOTE: Before installing the clock components, you might need to run the BuildObjs program found in the Utils directory. See the readme file on the CD that accompanies this chapter for additional information.


Listing 22.9. The header for the Clock component contains declarations for the clock and its property and component editors.

 
 
///////////////////////////////////////
 
// Clock1.h
 
// Clock Component
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#ifndef Clock1H
 
#define Clock1H
 
#include <vcl\sysutils.hpp>
 
#include <vcl\controls.hpp>
 
#include <vcl\classes.hpp>
 
#include <vcl\forms.hpp>
 
#include <vcl\dsgnintf.hpp>
 
#include <vcl\messages.hpp>
 
#include <vcl\extctrls.hpp>
 
 
 
class TClockAttributes: public TPersistent
 
{
 
private:
 
  TColor FColor;
 
  TShapeType FShape;
 
  TComponent *FOwner;
 
  void __fastcall SetColor(TColor AColor);
 
  void __fastcall SetShape(TShapeType AShape);
 
public:
 
  virtual __fastcall TClockAttributes() : TPersistent() { }
 
  virtual __fastcall TClockAttributes(TComponent *AOwner)
 
    : TPersistent() { FOwner = AOwner; }
 
__published:
 
  __property TColor Color={read=FColor, write=SetColor};
 
  __property TShapeType  Shape={read=FShape, write=SetShape};
 
};
 
 
 
////////////////////////////////////////
 
// TMyClock ////////////////////////////
 
////////////////////////////////////////
 
class TMyClock: public TCustomControl
 
{
 
private:
 
  int FTimer;
 
  Boolean FRunning;
 
  TClockAttributes *FClockAttributes;
 
  void __fastcall SetRunning(Boolean ARun);
 
protected:
 
  virtual __fastcall ~TMyClock() { delete FClockAttributes; }
 
  virtual void __fastcall Paint(void);
 
  void __fastcall WMTimer(TMessage &Message);
 
  void __fastcall WMDestroy(TMessage &Message);
 
public:
 
  virtual __fastcall TMyClock(TComponent* Owner);
 
__published:
 
  __property Boolean Running={read=FRunning, write=SetRunning};
 
  __property Align;
 
  __property TClockAttributes *ClockAttributes=
 
    {read=FClockAttributes, write=FClockAttributes };
 
  __property Font;
 
BEGIN_MESSAGE_MAP
 
  MESSAGE_HANDLER(WM_TIMER, TMessage, WMTimer);
 
  MESSAGE_HANDLER(WM_DESTROY, TMessage, WMDestroy);
 
END_MESSAGE_MAP(TCustomControl);
 
};
 
 
 
////////////////////////////////////////
 
// TColorClock /////////////////////////
 
////////////////////////////////////////
 
class TColorClock: public TMyClock
 
{
 
private:
 
  TColor FFaceColor;
 
  void __fastcall SetColor(TColor Color);
 
protected:
 
  void virtual __fastcall Paint(void);
 
public:
 
  virtual __fastcall TColorClock(TComponent *Owner)
 
    :TMyClock(Owner) { FFaceColor = clGreen; }
 
__published:
 
  __property TColor FaceColor={read=FFaceColor, write=SetColor, nodefault};
 
};
 
 
 
////////////////////////////////////////
 
// TClockEditor ////////////////////////
 
////////////////////////////////////////
 
class TClockEditor: public TComponentEditor
 
{
 
protected:
 
  virtual __fastcall void Edit(void);
 
public:
 
  virtual __fastcall TClockEditor(TComponent *AOwner, TFormDesigner *Designer)
 
    : TComponentEditor(AOwner, Designer) {}
 
};
 
 
 
////////////////////////////////////////
 
// TColorNameProperty /////////////////
 
////////////////////////////////////////
 
class TColorNameProperty: public TClassProperty
 
{
 
public:
 
  TColorNameProperty(): TClassProperty() {}
 
  TPropertyAttributes __fastcall GetAttributes(void);
 
};
 
 
 

#endif

Listing 22.10. The code for the Clock component should be kept in the Utils subdirectory where you store CodeBox and other utility units.

 
 
///////////////////////////////////////
 
// Clock1.cpp
 
// Clock Component
 
// Copyright (c) 1997 by Charlie Calvert
 
//
 
#include <vcl\vcl.h>
 
#pragma hdrstop
 
 
 
#include "Clock1.h"
 
 
 
///////////////////////////////////////
 
// ValidControlCheck
 
///////////////////////////////////////
 
static inline TMyClock *ValidCtrCheck()
 
{
 
  return new TMyClock(NULL);
 
}
 
 
 
///////////////////////////////////////
 
// TColorsProperty
 
///////////////////////////////////////
 
void __fastcall TClockAttributes::SetColor(TColor AColor)
 
{
 
  FColor = AColor;
 
  ((TControl *)(FOwner))->Invalidate();
 
};
 
 
 
void __fastcall TClockAttributes::SetShape(TShapeType AShape)
 
{
 
  FShape = AShape;
 
  ((TControl *)(FOwner))->Invalidate();
 
};
 
 
 
///////////////////////////////////////
 
// Constructor
 
///////////////////////////////////////
 
__fastcall TMyClock::TMyClock(TComponent* Owner)
 
  : TCustomControl(Owner)
 
{
 
  Width = 100;
 
  Height = 100;
 
  FTimer = 1;
 
  FClockAttributes = new TClockAttributes(this);
 
  FClockAttributes->Color = clBtnFace;
 
  FClockAttributes->Shape = stEllipse;
 
}
 
 
 
///////////////////////////////////////
 
// SetRunning
 
///////////////////////////////////////
 
void __fastcall TMyClock::SetRunning(Boolean Run)
 
{
 
  if (Run)
 
  {
 
    SetTimer(Handle, FTimer, 50, NULL);
 
    FRunning = True;
 
  }
 
  else
 
  {
 
    KillTimer(Handle, FTimer);
 
    FRunning = False;
 
  }
 
}
 
 
 
///////////////////////////////////////
 
// Paint
 
///////////////////////////////////////
 
void __fastcall TMyClock::Paint(void)
 
{
 
  Color = ClockAttributes->Color;
 
  switch (ClockAttributes->Shape)
 
  {
 
    int X;
 
    
 
    case stEllipse: Canvas->Ellipse(0, 0, Width, Height); break;
 
    case stRectangle: Canvas->Rectangle(0, 0, Width, Height); break;
 
    case stRoundRect:
 
      Canvas->RoundRect(0, 0, Width, Height, Width - 100, Height);
 
      break;
 
      
 
    case stSquare:
 
    {
 
      if (Width < Height)
 
      {
 
        X = Width / 2;
 
        Canvas->Rectangle(Width - X, 0, Width + X, Width);
 
      }
 
      else
 
      {
 
        X = Height / 2;
 
        Canvas->Rectangle((Width / 2) - X, 0, (Width / 2) + X, Height);
 
      }
 
      break;
 
    }
 
 
 
    case stCircle:
 
    {
 
      if (Width < Height)
 
      {
 
        X = Width / 2;
 
        Canvas->Ellipse(Width - X, 0, Width + X, Width);
 
      }
 
      else
 
      {
 
        X = Height / 2;
        Canvas->Ellipse((Width / 2) - X, 0, (Width / 2) + X, Height);
      }
      break;
    }
  }
}
 

Next (Part 02) >>

 

VMS Desenvolvimentos

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