Creating Descendants of Existing Components
– Part 02
///////////////////////////////////////
// WM_TIMER
///////////////////////////////////////
void __fastcall TMyClock::WMTimer(TMessage &Message)
{
AnsiString S = TimeToStr(Time());
Canvas->Font = Font;
Canvas->TextOut(Width / 2 - Canvas->TextWidth(S) / 2,
Height / 2 - Canvas->TextHeight(S) / 2, S);
}
///////////////////////////////////////
// WM_DESTROY
///////////////////////////////////////
void __fastcall TMyClock::WMDestroy(TMessage &Message)
{
KillTimer(Handle, FTimer);
FTimer = 0;
TCustomControl::Dispatch(&Message);
}
//------------------------------------
//-- TColorClock --------------------
//------------------------------------
///////////////////////////////////////
// WM_DESTROY
///////////////////////////////////////
void __fastcall TColorClock::Paint()
{
Canvas->Brush->Color = FFaceColor;
TMyClock::Paint();
}
///////////////////////////////////////
// SetColor
///////////////////////////////////////
void __fastcall TColorClock::SetColor(TColor Color)
{
FFaceColor = Color;
InvalidateRect(Handle, NULL, True);
}
//------------------------------------
//-- TClockEditor --------------------
//------------------------------------
void __fastcall TClockEditor::Edit(void)
{
TColorDialog *C = new TColorDialog(Application);
if (C->Execute())
((TColorClock *)(Component))->FaceColor = C->Color;
}
//------------------------------------
//-- TColorNameProperty --------------
//------------------------------------
TPropertyAttributes __fastcall TColorNameProperty::GetAttributes(void)
{
return TPropertyAttributes() << paSubProperties;
}
///////////////////////////////////////
// Register
///////////////////////////////////////
namespace Clock1
{
void __fastcall Register()
{
TComponentClass classes[2] = {__classid(TMyClock), __classid(TColorClock)};
RegisterComponents("Unleash", classes, 1);
RegisterComponentEditor(__classid(TColorClock), __classid(TClockEditor));
RegisterPropertyEditor(__typeinfo(TClockAttributes),
__classid(TMyClock), "Colors", __classid(TColorNameProperty));
}
} Listing 22.11. The test bed for the clock component is stored in the TestClock subdirectory.
#include <vcl\vcl.h>
#pragma hdrstop
#include "Main.h"
#include "Clock1.h"
#pragma link "Clock1"
#pragma link "sampreg"
#pragma link "ClockProperty1"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::Create1Click(TObject *Sender)
{
MyClock = new TColorClock(this);
MyClock->Parent = this;
MyClock->Show();
}
void __fastcall TForm1::ToggleRunning1Click(TObject *Sender)
{
MyClock->Running = !MyClock->Running;
}
void __fastcall TForm1::SetColor1Click(TObject *Sender)
{
if (ColorDialog1->Execute())
{
MyClock->FaceColor = ColorDialog1->Color;
}
} The code for the clock components uses inheritance, virtual methods, and properties. TClock has two pieces of private data: int FTimer;
Boolean FRunning;
One is an identifier for the timer, and the other is a Boolean value that specifies whether the clock is running. Windows timers are managed by two Windows API calls. When you want to start a timer, use SetTimer; when you want to stop a timer, use KillTimer. SetTimer takes four parameters:
A typical call to SetTimer looks like this: SetTimer(Handle, FTimer, 50, NULL);
50 specifies that the timer is called once every 50 milliseconds. SetTimer returns zero if the call fails. This is a real possibility, so programs that include error checking should inspect this value and put up a MessageBox if the call fails. KillTimer takes two parameters, the first being the handle of your window and the second being the unique identifier associated with that timer: KillTimer(Handle, FTimer);
When you're not using the callback function, timer events are sent to your window by way of messages: void __fastcall WMTimer(TMessage &Message);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_TIMER, TMessage, WMTimer);
MESSAGE_HANDLER(WM_DESTROY, TMessage, WMDestroy);
END_MESSAGE_MAP(TCustomControl);
This classic dynamic method is of the kind covered in Chapter 4, "Events." The response to this event is a simple procedure that calls TextOut and gets the time from the VCL: void __fastcall TMyClock::WMTimer(TMessage &Message)
{
AnsiString S = TimeToStr(Time());
Canvas->TextOut(Width / 2 - Canvas->TextWidth(S) / 2,
Height / 2 - Canvas->TextHeight(S) / 2, S);
}
The calls to SetTimer and KillTimer are managed primarily through a property called Running: __property Boolean Running={read=FRunning, write=SetRunning};
The write mechanism, a procedure called SetRunning, is a fairly straightforward tool: void __fastcall TMyClock::SetRunning(Boolean Run)
{
if (Run)
{
SetTimer(Handle, FTimer, 50, NULL);
FRunning = True;
}
else
{
KillTimer(Handle, FTimer);
FRunning = False;
}
}
If the user sets the Running property to True, this procedure is executed and a call is made to the SetTimer function. If the user sets Running to False, KillTimer is called and the clock immediately stops functioning. The final issue involving the timer concerns the case in which the user closes a form while the clock is still running. In such a case, you must be sure to call KillTimer before the application exits. If you don't make the call, the timer keeps running even after the application closes. Having the timer running wastes system resources and also uses up one of the dozen or so timers that are available to the system at any one time. The logical place to call KillTimer is the Destroy method for the TClock object. Unfortunately, the window associated with the clock has already been destroyed by the time this call is made, so no valid handle is available for use when you call KillTimer. As a result, you need to respond to WM_DESTROY messages to be sure the timer is killed before the TClock window is closed: void __fastcall TMyClock::WMDestroy(TMessage &Message)
{
KillTimer(Handle, FTimer);
FTimer = 0;
TCustomControl::Dispatch(&Message);
}
Before leaving this description of the TClock object, I should briefly mention the Paint method. Here is the simplest possible version of the paint procedure for the program: void __fastcall TMyClock::Paint(void)
{
Canvas->Ellipse(0, 0, 100, 100);
}
This procedure is called whenever the circle defining the circumference of the clock needs to be repainted. You never have to check for this circumstance, and you never have to call Paint directly. Windows keeps an eye on the TClock window, and if the window needs to be painted, Windows sends you a WM_PAINT message. Logic buried deep in the VCL converts the WM_PAINT message into a call to Paint, the same way TClock translates WM_TIMER messages into calls to TCanvas->TextOut. The actual paint procedure used in the program looks like this: ///////////////////////////////////////
// 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;
}
}
}
This version of the Paint procedure takes into account the ClockAttributes property of TMyClock. TClockAttributes looks like this: 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};
};
As you can see, it provides a shape and a background color for the clock. The most important aspect of the TClockAttributes type is the property editors I create for it. These editors let you access both properties of this class through a series of special techniques, described later in this chapter. For now, you might want to merely click the ClockAttributes property, open up its two fields, and see how it works. The TColorClock component, which is a descendant of TClock, adds color to the face of the control. I made TColorClock a separate object, instead of just adding a face color to TClock, for two different reasons (both of which are related to design):
TColorClock declares a private data store called FColor that is of type TColor. Users can set the FColor variable by manipulating the Color property: __property TColor Color={read=FColor, write=SetColor};
SetColor is a simple procedure that sets the value of FColor and calls the Windows API call InvalidateRect: void SetColor(TColor Color)
{ FColor = Color; InvalidateRect(Handle, NULL, True); }
Just to be sure this information makes sense to you, taking a paragraph or two to provide a refresher course on InvalidateRect is worthwhile. Here's the declaration for the routine: BOOL InvalidateRect(
HWND hWnd, // handle of window with changed update region
CONST RECT * lpRect, // address of rectangle coordinates
BOOL bErase // erase-background flag
);
InvalidateRect forces the window specified in the first parameter to redraw itself completely if the third parameter is set to True. If the third parameter is set to False, only the portions of the window that you specifically repaint are changed. The middle parameter is a pointer to the RECT structure that can be used to define the area that you want to redraw. Compare this function with the native BCB function called Invalidate. Calls to InvalidateRect naturally force calls to the TColorClick->Paint method: void __fastcall TColorClock::Paint()
{
Canvas->Brush->Color = FColor;
TMyClock::Paint();
}
Paint sets the brush associated with the window's device context to the color specified by the user; then it calls the Paint method defined in TClock. To add this object to the Component Palette, you must first register it: void __fastcall Register()
{
TComponentClass classes[2] = {__classid(TMyClock), __classid(TColorClock)};
RegisterComponents("Unleash", classes, 1);
... // Code omitted here
};
Here, I specify that the TClock and TColorClock objects should be placed in a group called Unleash. A number of properties implemented by TCustomControl and its ancestors do not surface automatically. To access these properties from the Component Palette, all you have to do is list them in the __published section of your class declaration: __property Align;
__property Color;
__property Font;
These simple declarations give your component the ability to align itself with the surface on which it resides or set its color and font. The color, in this case, is the color of the background of the form, not of the face of the form. In the actual finished version of the control, I ended up replacing the Color property with the TClockAttributes property. Once again, the primary reason for adding the TClockAttributes property was to show you how to work with property editors. There is, of course, a place where the Align, Color, and Font properties are declared replete with read and write parts. However, after you have declared the property, you can publish it simply by using the two-word declarations shown here. On
the CD that accompanies this book, you will find a program called GoldClock that uses the TClock component to create a small
application displaying the time. You can customize the fonts and colors in
this program, and the settings you make will be written to the Registry so
that they can be preserved for the next run of the program. The main form
of the GoldClock application is shown in Figure 22.9.
Figure 22.9.The main form of the GoldClock application. That's all I'm going to say about TClock and TColorClock. Overall, these components are fairly simple; they're interesting primarily because they show you how to go about constructing your own controls from scratch. This kind of exercise lies very much at the heart of BCB's architecture, and I expect many readers will be spending most of their time engaged almost exclusively in the business of building components. Tips on Creating PropertiesA standard technique used in component development features base classes that do not publish any properties. Consumers of the component can then inherit the functionality of the class without having to stick with the original developer's choice of published properties. After a property has been published, there is no good way to "unpublish" it. The solution to this potential problem is to create base classes that do not publish any properties. The functionality is present, but it is not surfaced in a way that it can be viewed in the Object Inspector. In many cases, the VCL creates classes that do nothing but publish the properties of their immediate ancestors. These classes sport no functions, no methods, nothing but a __published section full of declarations like the ones shown in the preceding section for the Align, Color, and Font properties. This architecture lets others inherit the capability of the class while still providing a choice regarding what will be published. The naming convention used for classes of this type usually involves the word Custom: TCustomImageList, TCustomControl, TCustomMemoryStream, TCustomGroupBox, TCustomLabel, TCustomEdit, TCustomMemo, TCustomCheckBox, and so on. Open StdCtrls.hpp to see more explicitly how this system works. Making descendants from TEdit or TLabel as I do in this chapter is a perhaps a bit too simplistic. If you really want to create a custom version of the Edit control for your own purposes, you should probably make descendants from TCustomEdit. Once again, you would not miss any functionality by doing so because TEdit does nothing but surface the "hidden" properties of TCustomEdit. It has no methods or data of its own; everything is inherited from TCustomEdit. All it does is publish 20-odd properties. You probably should not declare the read and write parts of a property in a published section because doing so forces consumers of the product to conform with your ideas for the component's interface. Instead, read and write declarations should go in the public section of a class that has Custom in the name. Then you should create a descendant of that class which actually publishes the property. Creating Icons for ComponentsThe icon associated with a component and placed in the Component Palette is defined in a file with a .DCR extension. If you do not provide this file, BCB uses the icon associated with the object's parent. If no icon is present anywhere in the component's ancestry, a default icon is used. NOTE: A DCR file is a Windows resource file with the extension changed from .RES to .DCR. The resource file contains a bitmap resource with the same name as your component. For example, the bitmap resource in a DCR file for a TCOLOR component would have a resource ID of TColor. This resource should be a 56x28 pixel (or smaller) bitmap that can be edited in the Image Editor. All you need to do is place this DCR file in the same directory as your component, and the images defined therein will show up on the Component Palette. Use the Image Editor to explore the DCR files that ship with BCB. They are stored in the \CBUILDER\LIB directory. I also ship some DCR files with this book in the Utils subdirectory where CodeBox and other files are kept. Follow these steps to associate your own bitmaps with a particular component. 1. Open the Image Editor from the Tools menu and choose New. If you don't like the Image Editor, you can create a 24x24 bitmap in Pbrush.exe or some other bitmap editor and then create an RC file that looks like this: TMYCLOCK BITMAP clock.bmp
Save the file as Clock1.rc. Run the Borland Resource Compiler from the command line: brcc32 -r clock.rc
The resulting file will be called Clock1.res. Rename that file Clock1.dcr. Creating Help Files for ComponentsBCB enables you to define help files that can ship with your components. These help files can be merged into the help that ships with BCB, so users of your product will feel as though it is a native part of BCB. The HLP file is a standard Windows help file that contains information about the properties, methods, and events implemented by your component. I strongly recommend that you obtain a third-party tool to aid in the construction of these help files. For example, you might buy a copy of FOREHELP from Borland, or you could turn to Blue Sky software, or some other third party that provides tools to help you create these files. As a last resort, you can use Word, WordPerfect, or even the TRichEdit component, to create RTF files, and then compile these RTF files into HLP files with the HCW.EXE help compiler that ships with BCB in the CBUILDER\HELP\Tools directory. You can also download this file from online sites such as the Microsoft Web site or CompuServe. (The copy of FOREHELP sold by Borland has not been upgraded for Windows 95, but it is still many times better than having no help tool at all. FOREHELP requires that you have a copy of HC.EXE or HCP.EXE on your system. These files ship with many compilers and are available royalty free on CompuServe and the Internet.) All the tools mentioned here play a peripheral role in the creation of components. They don't require a knowledge of the fascinating syntax used in component creation, but they help to make your tools attractive and easy to use. The Five Main Tools APIsEach of the five main Tools APIs is accessible through a separate set of routines that ship with BCB. These APIs enable you to write code that can be linked directly into the BCB IDE. Specifically, you can link your tools into CmpLib32.ccl the same way you link in components. Here is a list of the Tools APIs and the native BCB source files that define them. Experts: Enable you to write your own experts.
Version Control: Enables you to write your own Version Control system or to link in a third-party system.
Component Editors: Create dialogs associated with a control at design time (for example, the DataSet Designer is a component editor).
Property Editors: Create editors for use in the Object Inspector.
Editor Interfaces: For use mostly by third-party editor vendors such as Premia, American Cybernetics, and so on.
The letters INTF are an abbreviation for the word "Interface." This term was chosen because the Tools API is an interface between your own code and the BCB developers' code. Needless to say, most people will never use the Tools API. However, it will be important to a small minority of developers, and its existence means that everyone will be able to buy or download tools that extend the functionality of the IDE. Property EditorsThe Tools API for creating property editors is perhaps the most commonly used interface into the heart of the IDE. When you first use BCB and start becoming familiar with the Object Inspector, you are bound to think that it is a static element that never changes. However, by now it should come as no surprise to learn that you can change the functionality of the Object Inspector by adding new property editors to it. As I mentioned earlier, property editors control what takes place on the right side of the Properties page of the Object Inspector. In particular, when you click the Color property of TEdit, you can select a new color from a drop-down list, from a common dialog, or by typing in a new value. In all three cases, you're using a property editor. If you want to create a new property editor, you should create a descendant of TPropertyEditor, a class declared in DsgnIntf.hpp. TPropertyEditor has a number of children. Many of these are for handling simple types such as strings and integers. These editors are not very important to BCB programmers, because most simple types have no RTTI associated with them in C++. Instead of working with simple types, BCB programmers work with properties that are class types. The classic example of a class type of Property is TFont. Note, for instance, that the Font property of a form can be expanded to show the several fields of the TFont object. You can also pop up a dialog to edit the TFont object. This is a complex property editor of the type that will be developed through the remainder of this chapter. Here is the declaration for the property editor associated with the ClockAttributes property of the TMyClock component: class TColorNameProperty: public TClassProperty
{
public:
TColorNameProperty(): TClassProperty() {}
TPropertyAttributes __fastcall GetAttributes(void);
};
TColorNameProperty doesn't descend from TPropertyEditor directly. Instead, it descends from a child of TPropertyEditor called TClassEditor. TClassEditor is designed for programmers who want to create editors for complex properties that are also classes. The TColorNameProperty editor doesn't pop up any dialogs, although it will in a version of this program shown at the end of this chapter. Instead of creating dialogs, it simply tells the Object Inspector that the ClockAttributes class can be edited as two properties. In particular, notice that the ClockAttributes class expands out to show a Color property and a Shape property. You need to click the plus sign in front of the ClockAttributes property before you can see these properties. The GetAttributes method is a way of defining what types of property editors you want to have associated with TColorDialog: TPropertyAttributes __fastcall TColorNameProperty::GetAttributes(void)
{
return TPropertyAttributes() << paSubProperties;
}
In this case I have set the paSubProperties flag, which tells the Object Inspector to place the plus sign before the property so it can be opened up to reveal sub properties. A property editor that has the paMultiSelect flag remains active even if the user has selected more than one component of that type. For example, you can select 10 edit controls and change all their fonts in one step. BCB enables you to make this change because the TEdit control has the paMultiSelect flag set. The paValueList flag dictates that the property editor drops down a list of values from an enumerated or set type when the user clicks the arrow button at the far right of the editor. This functionality is built into BCB, and you need only set the flag to have it be supported by your property editor. Finally, paDialog states that the property editor pops up a dialog. Ultimately, the paDialog flag does little more than assure that the ellipsis button appears at the right of the property editor. NOTE: When you choose both paDialog and paValuelist in a single component, the property editor button always winds up being a combo drop-down list button. In other words, the dialog button is obscured, even though the functionality is still present. See, for example, the Color property of a TForm or TEdit. Streaming Properties of Type ClassThe ClockAttributes property will not work properly unless it is streamed to disk. In particular, effective use of TClockAttributes depends on whether the two key properties in TClockAttributes get written to the DFM file where the form is stored. To ensure that this happens, all you have to do is declare ClockAttributes as a published property, and then descend TClockAttributes from TPersistent, and make sure that the Shape and Color properties are published. Here is the declaration for the TClockAttributes: 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};
};
The key part of this declaration is where the Color and Shape properties are designated as published. This means that they will have RTTI associated with them, and that they will be streamed out to the DFM file automatically. Here is a text version of the TMyClock component as it appears in a DFM file: object MyClock2: TMyClock
Left = 112
Top = 192
Width = 100
Height = 100
ClockAttributes.Color = clBtnFace
ClockAttributes.Shape = stEllipse
end
Note that the two key properties of ClockAttributes have been included in the DFM file. I did not have to do anything special to make this happen. Instead, I just made sure that ClockAttributes itself was a published property, and that Color and Shape were published properties of TClockAttributes. Once I was sure the properties were being streamed out, the only thing left to do was tell the Object Inspector to show both values of TClockAttributes. To do this, I simply created the simple, do-nothing TColorNameProperty class. As you saw, this class descends from TClassProperty, which is declared in Dsdintf.hpp. Examining DsgnIntf.cppThe DsgnIntf unit is unusual in that it is very carefully documented by the developer who created it. For example, here are excerpts from that unit describing the two methods I call in the descendant of TPropertyEditor: Edit
Called when the `...' button is pressed or the
property is double-clicked. This can, for example,
bring up a dialog to allow the editing of the component
in some more meaningful fashion than by text
(e.g. the Font property).
GetAttributes
Returns the information for use in the Object
Inspector to be able to show the appropriate tools.
GetAttributes return a set of type TPropertyAttributes.
These entries were written by the developers, and they extensively document this important interface to the core code inside the heart of the IDE. Here are declarations for Edit and GetAttributes, as well as the other key functions in TPropertyEditor: class __declspec(pascalimplementation) TPropertyEditor : public System::TObject
public:
__fastcall virtual ~TPropertyEditor(void);
virtual void __fastcall Activate(void);
virtual bool __fastcall AllEqual(void);
virtual void __fastcall Edit(void);
virtual TPropertyAttributes __fastcall GetAttributes(void);
Classes::TComponent* __fastcall GetComponent(int Index);
virtual int __fastcall GetEditLimit(void);
virtual System::AnsiString __fastcall GetName(void);
virtual void __fastcall GetProperties(TGetPropEditProc Proc);
Typinfo::PTypeInfo __fastcall GetPropType(void);
virtual System::AnsiString __fastcall GetValue(void);
virtual void __fastcall GetValues(Classes::TGetStrProc Proc);
virtual void __fastcall Initialize(void);
void __fastcall Revert(void);
virtual void __fastcall SetValue(const System::AnsiString Value);
bool __fastcall ValueAvailable(void);
}
Once again, all these methods are carefully documented in DsgnIntf.hpp. You should study that file carefully in order to add to the knowledge about property and component editors outlined in the remainder of this chapter. Registering the Property EditorsYou must register property editors with the system before compiling them into CmpLib32.ccl. To register a property, call RegisterPropertyEditor in the Register method for your component: RegisterPropertyEditor(__typeinfo(TClockAttributes),
__classid(TMyClock), "Colors", __classid(TColorNameProperty));
The declaration for RegisterPropertyEditor looks like this: extern void __fastcall RegisterPropertyEditor(
Typinfo::PTypeInfo PropertyType,
System::TMetaClass* ComponentClass,
const System::AnsiString PropertyName,
System::TMetaClass* EditorClass);
The various parameters are as follow:
If you want to find out more about this function, refer to the comments in DsgnIntf.pas. These comments may not be copied into DsgnIntf.hpp, so check both places. Working with Component EditorsAfter you have learned how to build property editors, understanding component editors is easy. These tools are descendants of TComponentEditor, just as property editors are descendants of TPropertyEditor: class TClockEditor: public TComponentEditor
{
protected:
virtual __fastcall void Edit(void);
public:
virtual __fastcall TClockEditor(TComponent *AOwner, TFormDesigner *Designer)
: TComponentEditor(AOwner, Designer) {}
};
The TColorDialog has a simple editor that pops up a dialog requesting that the user choose a new color: void __fastcall TClockEditor::Edit(void)
{
TColorDialog *C = new TColorDialog(Application);
if (C->Execute())
((TColorClock *)(Component))->FaceColor = C->Color;
}
In this case, I'm popping up the standard TColorDialog that appears when you click the ellipsis icon in the Object Inspector for a property of type TColor. The difference, of course, is that this is a component editor, not a property editor. In the more complex component editor example shown later in this chapter, you pop open a form that allows the user to make several changes to the component. It is easy to access the underlying component from inside a component editor. In particular, the component you are editing is available to you in a variable called Component. The Component variable referenced in this example is declared globally inside TComponentEditor objects. To access it, you merely have to typecast it as desired to get at the component you're currently editing. For example, in this example, I typecast Component as a TColorClock. I don't bother with a dynamic_cast in this example because the conversion has to work, by the very definition of the context of the call. This
component editor, of course, is the simplest possible, but it gets you
started working with these useful tools. Figure 22.10 shows the component
editor in action.
The Register method for TClockEditor looks like this:
void __fastcall Register()
{
. . .
RegisterComponentEditor(__classid(TMyClock), __classid(TClockEditor));
. . .
}
}
The declaration for this procedure looks like this:
extern void __fastcall RegisterComponentEditor(System::TMetaClass* ComponentClass,
System::TMetaClass* ComponentEditor);
Clearly, the first parameter specifies the class with which the editor is associated, and the second parameter specifies the class of the editor. In this section, I introduced you to property editors and component editors. These examples are important primarily because they help focus your attention on DsgnIntf.hpp, which is one of several files that ship with BCB that define the Tools API. If you want to extend the BCB IDE, you should get to know all the files that end in INTF. Extending the Component and Property EditorsThe original component and property editors for TMyClock and TColorClock components are very simple. Of
course, using such simple component and property editors is very limiting.
You may use a normal VCL form as an editor. Examples of this type of form
are shown in Figures 22.11 and 22.12.
Figure
22.11.The new property editor for ClockAttributes is a VCL form that gives the
user considerable control over the appearance of the clock.
Figure 22.12.This component editor for TClock uses a standard VCL form and can be as complex as you want. To create the form shown in Figure 22.12, I started a new project called TestClock2. I then copied the Clock1 files into a new set of files called Clock2 and changed all the names inside the files to reflect the change. For example, I renamed TMyClock to TMyClock2, TColorClock to TColorClock2, and so on. I then created a new form called ClockEditor and added several buttons to it, a TColorDialog, and a TImage control. I arranged the controls as shown in Figure 22.12. I then made a few changes to the Edit method of TClockEditor2, and added a button to my main form so that I could test the editor. The source code for this new program is shown in Listings 22.12 through 22.15. 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. In particular, BuildObjs makes sure that ClockAttributes.cpp and ClockEditor.cpp are both compiled into binary form as OBJ files. Listing 22.12. The header file for the main unit of the TestClock2 program.
///////////////////////////////////////
// Main.h
// Project: Testing example of Component editor
// Copyright 1997 by Charlie Calvert
//
#ifndef MainH
#define MainH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include "Clock2.h"
#include "Clock1.h"
class TForm1 : public TForm
{
__published:
TButton *Button1;
TColorClock2 *ColorClock21;
void __fastcall Button1Click(TObject *Sender);
private:
public:
__fastcall TForm1(TComponent* Owner);
};
extern TForm1 *Form1;
#endif Listing 22.13. The main module for the TestClock2 program.
///////////////////////////////////////
// Main.cpp
// Project: Testing example of Component editor
// Copyright 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "Main.h"
#include "ClockEditor.h"
#pragma link "Clock2"
#pragma link "Clock1"
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::Button1Click(TObject *Sender)
{
RunClockEditorDlg(ColorClock21);
} Listing 22.14. The header for the ColorEditor dialog.
///////////////////////////////////////
// ClockEditor.h
// Project: Clock2 example of Component editor
// Copyright 1997 by Charlie Calvert
//
#ifndef ClockEditorH
#define ClockEditorH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\Dialogs.hpp>
#include "Clock2.h"
#include <vcl\ExtCtrls.hpp>
#include <vcl\Buttons.hpp>
void RunClockEditorDlg(TColorClock2 *Clock);
class TClockEditorDialog : public TForm
{
__published:
TButton *SetFaceColorBtn;
TColorDialog *ColorDialog1;
TButton *SetBackColorBtn;
TPanel *Panel1;
TImage *Image1;
TBitBtn *BitBtn1;
void __fastcall SetFaceColorBtnClick(TObject *Sender);
void __fastcall SetBackColorBtnClick(TObject *Sender);
private:
TColorClock2 *FClock;
public:
__fastcall TClockEditorDialog(TComponent* Owner);
__property TColorClock2 *Clock={read=FClock, write=FClock};
};
extern TClockEditorDialog *ClockEditorDialog;
#endif Listing 22.15. The main module for the ColorEditor dialog.
///////////////////////////////////////
// ClockEditor.cpp
// Project: Clock2 example of Component editor
// Copyright 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "ClockEditor.h"
#include "clock2.h"
#pragma resource "*.dfm"
TClockEditorDialog *ClockEditorDialog;
__fastcall TClockEditorDialog::TClockEditorDialog(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TClockEditorDialog::SetFaceColorBtnClick(TObject *Sender)
{
if (ColorDialog1->Execute())
{
Clock->FaceColor = ColorDialog1->Color;
}
}
void __fastcall TClockEditorDialog::SetBackColorBtnClick(TObject *Sender)
{
if (ColorDialog1->Execute())
{
Clock->ClockAttributes->Color = ColorDialog1->Color;
}
}
void RunClockEditorDlg(TColorClock2 *Clock)
{
ClockEditorDialog = new TClockEditorDialog(Application);
ClockEditorDialog->Clock = Clock;
ClockEditorDialog->ShowModal();
delete ClockEditorDialog;
} Listing 22.16. The header for the VCL form that serves a dialog for editing the ClockAttributes property.
///////////////////////////////////////
// ClockAttributes.h
// Clock Component with Component Editor
// Copyright (c) 1997 by Charlie Calvert
//
#ifndef ClockAttributesH
#define ClockAttributesH
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\Buttons.hpp>
#include "Clock2.h"
#include <vcl\Dialogs.hpp>
void RunClockAttributesDlg(TClockAttribute2 *AClockAttribute);
class TClockAttributesDialog : public TForm
{
__published:
TPanel *Panel1;
TComboBox *ComboBox1;
TBitBtn *GetColorsBtn;
TBitBtn *BitBtn2;
TColorDialog *ColorDialog1;
TImage *Image1;
void __fastcall GetColorsBtnClick(TObject *Sender);
void __fastcall BitBtn2Click(TObject *Sender);
private:
TClockAttribute2 *FClockAttribute;
public:
__fastcall TClockAttributesDialog(TComponent* Owner);
__property TClockAttribute2 *ClockAttribute =
{read=FClockAttribute, write=FClockAttribute};
};
extern TClockAttributesDialog *ClockAttributesDialog;
#endif Listing 22.17. The main source file for the VCL form that serves a dialog for editing the ClockAttributes property.
///////////////////////////////////////
// ClockAttributes.cpp
// Clock Component with Component Editor
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "ClockAttributes.h"
#pragma resource "*.dfm"
TClockAttributesDialog *ClockAttributesDialog;
__fastcall TClockAttributesDialog::TClockAttributesDialog(TComponent* Owner)
: TForm(Owner)
{
}
void RunClockAttributesDlg(TClockAttribute2 *AClockAttribute)
{
ClockAttributesDialog = new TClockAttributesDialog(Application);
ClockAttributesDialog->ClockAttribute = AClockAttribute;
ClockAttributesDialog->ComboBox1->ItemIndex = (int)AClockAttribute->Shape;
ClockAttributesDialog->ShowModal();
delete ClockAttributesDialog;
}
void __fastcall TClockAttributesDialog::GetColorsBtnClick(TObject *Sender)
{
if (ColorDialog1->Execute())
{
FClockAttribute->Color = ColorDialog1->Color;
}
}
void __fastcall TClockAttributesDialog::BitBtn2Click(TObject *Sender)
{
FClockAttribute->Shape = TShapeType(ComboBox1->ItemIndex);
} Listing 22.18. Here is the header for the new version of the clock components.
///////////////////////////////////////
// Clock2.h
// Clock Component
// Copyright (c) 1997 by Charlie Calvert
//
//------------------------------------
#ifndef Clock2H
#define Clock2H
#include <vcl\sysutils.hpp>
#include <vcl\controls.hpp>
#include <vcl\classes.hpp>
#include <vcl\forms.hpp>
#include <vcl\dsgnintf.hpp>
#include <vcl\messages.hpp>
class TClockAttribute2: public TPersistent
{
private:
TColor FColor;
TShapeType FShape;
TComponent *FOwner;
void __fastcall SetColor(TColor AColor);
void __fastcall SetShape(TShapeType AShape);
public:
virtual __fastcall TClockAttribute2(TComponent *AOwner)
: TPersistent() { FOwner = AOwner; }
__published:
__property TColor Color={read=FColor, write=SetColor};
__property TShapeType Shape={read=FShape, write=SetShape};
};
////////////////////////////////////////
// TMyClock ////////////////////////////
////////////////////////////////////////
class TMyClock2: public TCustomControl
{
private:
int FTimer;
Boolean FRunning;
TClockAttribute2 *FClockAttributes;
void __fastcall SetRunning(Boolean ARun);
protected:
virtual void __fastcall Paint(void);
void __fastcall WMTimer(TMessage &Message);
void __fastcall WMDestroy(TMessage &Message);
public:
virtual __fastcall TMyClock2(TComponent* Owner);
__published:
__property Boolean Running={read=FRunning, write=SetRunning};
__property Align;
__property TClockAttribute2 *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 TColorClock2: public TMyClock2
{
private:
TColor FFaceColor;
void __fastcall SetColor(TColor Color);
protected:
void virtual __fastcall Paint(void);
public:
virtual __fastcall TColorClock2(TComponent *Owner)
:TMyClock2(Owner) { FFaceColor = clGreen; }
__published:
__property TColor FaceColor={read=FFaceColor, write=SetColor, nodefault};
};
////////////////////////////////////////
// TClockEditor ////////////////////////
////////////////////////////////////////
class TClockEditor2: public TComponentEditor
{
protected:
virtual __fastcall void Edit(void);
public:
virtual __fastcall TClockEditor2(TComponent *AOwner, TFormDesigner *Designer)
: TComponentEditor(AOwner, Designer) {}
};
////////////////////////////////////////
// TColorNameProperty /////////////////
////////////////////////////////////////
class TClockAttributeProperty: public TClassProperty
{
public:
TClockAttributeProperty(): TClassProperty() {}
TPropertyAttributes __fastcall GetAttributes(void);
void virtual __fastcall Edit(void);
};
#endif Listing 22.19. The main source file for the new version of the clock components.
///////////////////////////////////////
// Clock2.cpp
// Clock Component with Component Editor
// Copyright (c) 1997 by Charlie Calvert
//
#include <vcl\vcl.h>
#pragma hdrstop
#include "Clock2.h"
#include "ClockEditor.h"
#include "ClockAttributes.h"
#pragma link "ClockEditor.obj"
#pragma link "ClockAttributes.obj"
///////////////////////////////////////
// ValidControlCheck
///////////////////////////////////////
static inline TMyClock2 *ValidCtrCheck()
{
return new TMyClock2(NULL);
}
///////////////////////////////////////
// TClockAttributes
///////////////////////////////////////
void __fastcall TClockAttribute2::SetColor(TColor AColor)
{
FColor = AColor;
((TControl *)(FOwner))->Invalidate();
};
void __fastcall TClockAttribute2::SetShape(TShapeType AShape)
{
FShape = AShape;
((TControl *)(FOwner))->Invalidate();
};
///////////////////////////////////////
// Constructor
///////////////////////////////////////
__fastcall TMyClock2::TMyClock2(TComponent* Owner)
: TCustomControl(Owner)
{
Width = 100;
Height = 100;
FTimer = 1;
FClockAttributes = new TClockAttribute2(this);
FClockAttributes->Color = clBtnFace;
FClockAttributes->Shape = stEllipse;
}
///////////////////////////////////////
// SetRunning
///////////////////////////////////////
void __fastcall TMyClock2::SetRunning(Boolean Run)
{
if (Run)
{
SetTimer(Handle, FTimer, 50, NULL);
FRunning = True;
}
else
{
KillTimer(Handle, FTimer);
FRunning = False;
}
}
///////////////////////////////////////
// Paint
///////////////////////////////////////
void __fastcall TMyClock2::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;
}
}
}
///////////////////////////////////////
// WM_TIMER
///////////////////////////////////////
void __fastcall TMyClock2::WMTimer(TMessage &Message)
{
AnsiString S = TimeToStr(Time());
Canvas->Font = Font;
Canvas->TextOut(Width / 2 - Canvas->TextWidth(S) / 2,
Height / 2 - Canvas->TextHeight(S) / 2, S);
}
///////////////////////////////////////
// WM_DESTROY
///////////////////////////////////////
void __fastcall TMyClock2::WMDestroy(TMessage &Message)
{
KillTimer(Handle, FTimer);
FTimer = 0;
TCustomControl::Dispatch(&Message);
}
//------------------------------------
//-- TColorClock2 --------------------
//------------------------------------
///////////////////////////////////////
// WM_DESTROY
///////////////////////////////////////
void __fastcall TColorClock2::Paint()
{
Canvas->Brush->Color = FFaceColor;
TMyClock2::Paint();
}
///////////////////////////////////////
// SetColor
///////////////////////////////////////
void __fastcall TColorClock2::SetColor(TColor Color)
{
FFaceColor = Color;
InvalidateRect(Handle, NULL, True);
}
//------------------------------------
//-- TClockEditor --------------------
//------------------------------------
void __fastcall TClockEditor2::Edit(void)
{
RunClockEditorDlg(((TColorClock2 *)(Component)));
}
//------------------------------------
//-- TColorNameProperty --------------
//------------------------------------
TPropertyAttributes __fastcall TClockAttributeProperty::GetAttributes(void)
{
return TPropertyAttributes() << paSubProperties << paDialog;
}
void __fastcall TClockAttributeProperty::Edit()
{
TClockAttribute2 *C = (TClockAttribute2 *)GetOrdValue();
RunClockAttributesDlg(C);
Modified();
}
///////////////////////////////////////
// Register
///////////////////////////////////////
namespace Clock2
{
void __fastcall Register()
{
TComponentClass classes[2] = {__classid(TMyClock2), __classid(TColorClock2)};
RegisterComponents("Unleash", classes, 1);
RegisterComponentEditor(__classid(TColorClock2), __classid(TClockEditor2));
RegisterPropertyEditor(__typeinfo(TClockAttribute2),
__classid(TMyClock2), "ClockAttributes",
__classid(TClockAttributeProperty));
}
} To handle the chore of creating the form, I created a special method inside the ClockEditor unit. The routine looks like this:
void RunClockEditorDlg(TColorClock2 *Clock)
{
ClockEditorDialog = new TClockEditorDialog(Application);
ClockEditorDialog->Clock = Clock;
ClockEditorDialog->ShowModal();
delete ClockEditorDialog;
}
This routine takes an instance of the clock that needs to be edited as its sole parameter. It then creates an instance of the TClockEditorDialog and assigns the clock to an internal data store of the dialog. Finally, the code shows the dialog and cleans up the memory when the user is through making the edits. To test this dialog, you can simply drop an instance of the clock onto the main form of the application and then call the dialog from a button click response method:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
RunClockEditorDlg(ColorClock21);
}
After you're sure that everything is working properly, you can change the TClockEditor2::Edit method so that it calls this dialog when the user double-clicks the component in design mode:
void __fastcall TClockEditor2::Edit(void)
{
RunClockEditorDlg(((TColorClock2 *)(Component)));
}
Once again, you can see that the code takes advantage of the Component object that is declared ahead of time by the VCL. All you need to do is typecast the object to the proper type and then pass it on to the ClockEditor object. Inside the TClockEditorDialog itself, the act of changing the component is trivial: void __fastcall TClockEditorDialog::SetFaceColorBtnClick(TObject *Sender)
{
if (ColorDialog1->Execute())
{
Clock->FaceColor = ColorDialog1->Color;
}
}
This code simply pops up a color dialog and allows the user to choose a new face color. If the user makes a selection, the color is assigned to the relevant property of the TColorClock2 object. I still need to address one subject in this project. The issue here is that the TClock2 project now consists of two different modules. The first module contains the TColorClock itself, and the second module contains the TClockEditorDialog. When you add the Clock2.cpp module to the Component Palette, you also have to find a way to bring in ClockEditor.cpp; otherwise, the project will not compile. Unfortunately, you can't add ClockEditor directly to the CmpLib32 project because ClockEditor does not have a Register method. Without the Register method, the component library won't know what to do with the file. The solution to this problem is provided by a pragma that can be added to Clock2.cpp to tell the compiler that it must include ClockEditor.obj in any projects that use this unit: #pragma link "ClockEditor.obj"
After you add this line, CmpLib32 will know that it has to include ClockEditor.obj in the compilation. It will not, however, know to compile the unit from a CPP file into an OBJ file. NOTE: In the first shipping version of
BCB, a problem can occur if you try to recompile CmpLib32.ccl and do not provide a
precompiled copy of ClockEditor.obj.
In particular, the compiler does not treat ClockEditor as part of the project it is making, so it will not know to make ClockEditor.obj from ClockEditor.cpp. That's all I'm going to say about component editors. As you have seen, this subject is not complicated, even when you create your own custom form for editing the object. Creating custom forms of this type can be very helpful for the user because it gives you a chance to step him or her through the process of initializing the object. You can even create a little expert that runs when the user double-clicks the component. The expert could take the user by the hand and ensure that he or she sets up your control properly. The property editor for the ClockAttributes property is very similar to the component editor. The primary difference is in the way you access the underlying value that you want to edit: void __fastcall TClockAttributeProperty::Edit()
{
TClockAttribute2 *C = (TClockAttribute2 *)GetOrdValue();
RunClockAttributesDlg(C);
Modified();
}
Instead of having a ready-to-use variable called Component, you need to call the GetOrdValue method of TPropertyEditor. This function was originally intended to retrieve simply ordinal values. It can, however, do double-duty and retrieve pointer values. This is possible because a pointer and an integer both consist of 4 bytes. In this case, I typecast the value returned from GetOrdValue as a variable of type TClockAttribute. I can then pass this value into my VCL form and edit it as I please. The principle here is exactly the same as in the Component editor, only this time I access the underlying property that I want to edit through GetOrdValue, rather than through a variable named Component. Inside the ClockAttributes dialog you need to create a routine that will pop open the VCL form: void RunClockAttributesDlg(TClockAttribute2 *AClockAttribute)
{
ClockAttributesDialog = new TClockAttributesDialog(Application);
ClockAttributesDialog->ClockAttribute = AClockAttribute;
ClockAttributesDialog->ComboBox1->ItemIndex = (int)AClockAttribute->Shape;
ClockAttributesDialog->ShowModal();
delete ClockAttributesDialog;
}
Most of this code is very routine and does nothing more than allocate memory for a form, show it modally, and then destroy it. Note the line that sets the value of the selected item in the form's combo box: ClockAttributesDialog->ComboBox1->ItemIndex = (int)AClockAttribute->Shape;
This
line accesses one of the fields of the TClockAttribute class. It then sets the ItemIndex of the combo box to the currently selected shape. Here are the values
displayed in the combo box: Square RoundRect RoundSquare Ellipse SummaryIn this chapter, you have learned about building components. Specifically, you learned how to create components that do the following:
You also learned about tools and files associated with components, such as the DCR and KWF files. Finally, you learned about the Tools API and specifically about the art of making property editors and component editors.
VMS Desenvolvimentos
Diversas Dicas, Apostilas, Arquivos Fontes,
Tutoriais, Vídeo Aula, Download de Arquivos Relacionado a Programação em C++
Builder.
|