This chapter describes the steps for making the components you create available in the IDE. Making your components available at design time requires several steps:
Not all these steps apply to every component. For example, if you don't define any new properties or events, you don't need to provide Help for them. The only steps that are always necessary are registration and compilation.
Once your components have been registered and compiled into packages, they can be distributed to other developers and installed in the IDE. For information on installing packages in the IDE, see "Installing component packages".
Registration works on a compilation unit basis, so if you create several components in a single compilation unit, you can register them all at once.
To register a component, add a Register procedure to the unit. Within the Register procedure, you register the components and determine where to install them on the Component palette.
Note: If you create your component by choosing Component|New Component in the IDE, the code required to register your component is added automatically.
The steps for manually registering a component are:
Registration involves writing a single procedure in the unit, which must have the name Register. The Register procedure must appear in the interface part of the unit.
The following code shows the outline of a simple unit that creates and registers new components:
unit MyBtns;
interface
type
... { declare your component types here }
procedure Register; { this must appear in the interface section }
implementation
... { component implementation goes here }
procedure Register;
begin
... { register the components }
end;
end.
Within the Register procedure, call RegisterComponents for each component you want to add to the Component palette. If the unit contains several components, you can register them all in one step.
Inside the Register procedure of a unit containing components, you must register each component you want to add to the Component palette. If the unit contains several components, you can register them at the same time.
To register a component, call the RegisterComponents procedure once for each page of the Component palette to which you want to add components. RegisterComponents involves three important things:
Within the Register procedure, pass the component names in an open array, which you can construct inside the call to RegisterComponents.
RegisterComponents('Miscellaneous', [TMyComponent]);
You could also register several components on the same page at once, or register components on different pages, as shown in the following code:
procedure Register;
begin
RegisterComponents('Miscellaneous', [TFirst, TSecond]); { two on this page... }
RegisterComponents('Assorted', [TThird]); { ...one on another... }
RegisterComponents(LoadStr(srStandard), [TFourth]); { ...and one on the Standard page }
end;
The palette-page name is a string. If the name you give for the palette page does not already exist, Delphi creates a new page with that name. Delphi stores the names of the standard pages in string-list resources so that international versions of the product can name the pages in their native languages. If you want to install a component on one of the standard pages, you should obtain the string for the page name by calling the LoadStr function, passing the constant representing the string resource for that page, such as srSystem for the System page.
Within the Register procedure, call RegisterComponents to register the components in the classes array. RegisterComponents is a function that takes three parameters: the name of a Component palette page, the array of component classes, and the index of the last entry in the array.
Set the Page parameter to the name of the page on the component palette where the components should appear. If the named page already exists, the components are added to that page. If the named page does not exist, Delphi creates a new palette page with that name.
Call RegisterComponents from the implementation of the Register procedure in one of the units that defines the custom components. The units that define the components must then be compiled into a package and the package must be installed before the custom components are added to the component palette.
procedure Register;
begin
RegisterComponents('System', [TSystem1, TSystem2]); {add to system page}
RegisterComponents('MyCustomPage',[TCustom1, TCustom2]); { new page}
end;
Every component needs a bitmap to represent the component on the Component palette. If you don't specify your own bitmap, Delphi uses a default bitmap.
Because the palette bitmaps are needed only at design time, you don't compile them into the component's compilation unit. Instead, you supply them in a Windows resource file with the same name as the unit, but with the extension .DCR (dynamic component resource). You can create this resource file using the Image editor in Delphi. Each bitmap should be 24 pixels square.
For each component you want to install, supply a palette bitmap file, and within each palette bitmap file, supply a bitmap for each component you register. The bitmap image has the same name as the component. Keep the palette bitmap file in the same directory with the compiled files, so Delphi can find the bitmaps when it installs the components on the Component palette.
For example, if you create a component named TMyControl in a unit named ToolBox, you need to create a resource file called TOOLBOX.DCR that contains a bitmap called TMYCONTROL. The resource names are not case-sensitive, but by convention they are usually in uppercase letters.
When you select a standard component on a form, or a property or event in the Object Inspector, you can press F1 to get Help on that item. You can provide developers with the same kind of documentation for your components if you create the appropriate Help files.
You can provide a small Help file to describe your components, and your help file becomes part of the user's overall Delphi Help system.
See the section "Creating the Help file" for information on how to compose the help file for use with a component.
You can use any tool you want to create the source file for a Windows Help file (in .rtf format). Delphi includes the Microsoft Help Workshop, which compiles your Help files and provides an online help authoring guide. You can find complete information about creating Help files in the online guide for Help Workshop.
Composing help files for components consists of the steps:
To make your component's Help integrate seamlessly with the Help for the rest of the components in the library, observe the following conventions:
The component topic should show which unit the component is declared in and briefly describe the component. The component topic should link to secondary windows that describe the component's position in the object hierarchy and list all of its properties, events, and methods. Application developers access this topic by selecting the component on a form and pressing F1. For an example of a component topic, place any component on a form and press F1.
The component topic must have a # footnote with a value unique to the topic. The # footnote uniquely identifies each topic by the Help system.
The component topic should have a K footnote for keyword searching in the help system Index that includes the name of the component class. For example, the keyword footnote for the TMemo component is "TMemo."
The component topic should also have a $ footnote that provides the title of the topic. The title appears in the Topics Found dialog box, the Bookmark dialog box, and the History window.
Links to object classes, properties, methods, or events in the Delphi help system can be made using Alinks. When linking to an object class, the Alink uses the class name of the object, followed by an underscore and the string "object". For example, to link to the TCustomPanel object, use the following:
!AL(TCustomPanel_object,1)
When linking to a property, method, or event, precede the name of the property, method, or event by the name of the object that implements it and an underscore. For example, to link to the Text property which is implemented by TControl, use the following:
!AL(TControl_Text,1)
To see an example of the secondary navigation topics, display the help for any component and click on the links labeled hierarchy, properties, methods, or events.
A property, event, or method topic should show the declaration of the item and describe its use. Application developers see these topics either by highlighting the item in the Object Inspector and pressing F1 or by placing the cursor in the Code editor on the name of the item and pressing F1. To see an example of a property topic, select any item in the Object Inspector and press F1.
The property, event, and method topics should include a K footnote that lists the name of the property, method, or event, and its name in combination with the name of the component. Thus, the Text property of TControl has the following K footnote:
Text,TControl;TControl,Text;Text,
The property, method, and event topics should also include a $ footnote that indicates the title of the topic, such as TControl.Text.
All of these topics should have a topic ID that is unique to the topic, entered as a # footnote.
Each component, property, method, and event topic must have an A footnote. The A footnote is used to display the topic when the user selects a component and presses F1, or when a property or event is selected in the Object Inspector and the user presses F1. The A footnotes must follow certain naming conventions:
If the Help topic is for a component, the A footnote consists of two entries separated by a semicolon using this syntax:
ComponentClass_Object;ComponentClass
where ComponentClass is the name of the component class.
If the Help topic is for a property or event, the A footnote consists of three entries separated by semicolons using this syntax:
ComponentClass_Element;Element_Type;Element
where ComponentClass is the name of the component class, Element is the name of the property, method, or event, and Type is the either Property, Method, or Event
For example, for a property named BackgroundColor of a component named TMyGrid, the A footnote is
TMyGrid_BackgroundColor;BackgroundColor_Property;BackgroundColor
To add your Help file to Delphi, use the OpenHelp utility (called oh.exe) located in the bin directory or accessed using Help|Customize in the IDE.
You will find information about using OpenHelp in the OpenHelp.hlp file, including adding your Help file to the Help system.
The Object Inspector provides default editing for all types of properties. You can, however, provide an alternate editor for specific properties by writing and registering property editors. You can register property editors that apply only to the properties in the components you write, but you can also create editors that apply to all properties of a certain type.
At the simplest level, a property editor can operate in either or both of two ways: displaying and allowing the user to edit the current value as a text string, and displaying a dialog box that permits some other kind of editing. Depending on the property being edited, you might find it useful to provide either or both kinds.
Writing a property editor requires these five steps:
The DsgnIntf unit defines several kinds of property editors, all of which descend from TPropertyEditor. When you create a property editor, your property-editor class can either descend directly from TPropertyEditor or indirectly through one of the property-editor classes described in Table 38.1.
The DsgnIntf unit also defines some very specialized property editors used by unique properties such as the component name. The listed property editors are the ones that are the most useful for user-defined properties.
The following example shows the declaration of a simple property editor named TMyPropertyEditor:
type TFloatProperty = class(TPropertyEditor) public function AllEqual: Boolean; override; function GetValue: string; override; procedure SetValue(const Value: string); override; end;
All properties need to provide a string representation of their values for the Object Inspector to display. Most properties also allow the user to type in a new value for the property. Property-editor classes provide virtual methods you can override to convert between the text representation and the actual value.
The methods you override are called GetValue and SetValue. Your property editor also inherits a set of methods used for assigning and reading different sorts of values, as shown in Table 38.2
.
When you override a GetValue method, you will call one of the Get methods, and when you override SetValue, you will call one of the Set methods.
The property editor's GetValue method returns a string that represents the current value of the property. The Object Inspector uses this string in the value column for the property. By default, GetValue returns unknown.
To provide a string representation of your property, override the property editor's GetValue method.
If the property is not a string value, GetValue must convert the value into a string representation.
The property editor's SetValue method takes a string typed by the user in the Object Inspector, converts it into the appropriate type, and sets the value of the property. If the string does not represent a proper value for the property, SetValue should throw an exception and not use the improper value.
To read string values into properties, override the property editor's SetValue method.
SetValue should convert the string and validate the value before calling one of the Set methods.
Here are the GetValue and SetValue methods for TIntegerProperty. Integer is an ordinal type, so GetValue calls GetOrdValue and converts the result to a string. SetValue converts the string to an integer, performs some range checking, and calls SetOrdValue.
function TIntegerProperty.GetValue: string;
begin
Result := IntToStr(GetOrdValue);
end;
procedure TIntegerProperty.SetValue(const Value: string);
var
L: Longint;
begin
L := StrToInt(Value); { convert string to number }
with GetTypeData(GetPropType)^ do { this uses compiler data for type Integer }
if (L < MinValue) or (L > MaxValue) then { make sure it's in range... }
raise EPropertyError.Create( { ...otherwise, raise exception }
FmtLoadStr(SOutOfRange, [MinValue, MaxValue]));
SetOrdValue(L); { if in range, go ahead and set value }
end;
The specifics of the particular examples here are less important than the principle: GetValue converts the value to a string; SetValue converts the string and validates the value before calling one of the "Set" methods.
You can optionally provide a dialog box in which the user can visually edit a property. The most common use of property editors is for properties that are themselves classes. An example is the Font property, for which the user can open a font dialog box to choose all the attributes of the font at once.
To provide a whole-property editor dialog box, override the property-editor class's Edit method.
Edit methods use the same Get and Set methods used in writing GetValue and SetValue methods. In fact, an Edit method calls both a Get method and a Set method. Because the editor is type-specific, there is usually no need to convert the property values to strings. The editor generally deals with the value "as retrieved."
When the user clicks the '...' button next to the property or double-clicks the value column, the Object Inspector calls the property editor's Edit method.
Within your implementation of the Edit method, follow these steps:
The Color properties found in most components use the standard Windows color dialog box as a property editor. Here is the Edit method from TColorProperty, which invokes the dialog box and uses the result:
procedure TColorProperty.Edit;
var
ColorDialog: TColorDialog;
begin
ColorDialog := TColorDialog.Create(Application); { construct the editor }
try
ColorDialog.Color := GetOrdValue; { use the existing value }
if ColorDialog.Execute then { if the user OKs the dialog... }
SetOrdValue(ColorDialog.Color); { ...use the result to set value }
finally
ColorDialog.Free; { destroy the editor }
end;
end;
The property editor must provide information that the Object Inspector can use to determine what tools to display. For example, the Object Inspector needs to know whether the property has subproperties or can display a list of possible values.
To specify editor attributes, override the property editor's GetAttributes method.
GetAttributes is a method that returns a set of values of type TPropertyAttributes that can include any or all of the following values:
Color properties are more versatile than most, in that they allow several ways for users to choose them in the Object Inspector: typing, selection from a list, and customized editor. TColorProperty's GetAttributes method, therefore, includes several attributes in its return value:
function TColorProperty.GetAttributes: TPropertyAttributes; begin Result := [paMultiSelect, paDialog, paValueList]; end;
Once you create a property editor, you need to register it with Delphi. Registering a property editor associates a type of property with a specific property editor. You can register the editor with all properties of a given type or just with a particular property of a particular type of component.
To register a property editor, call the RegisterPropertyEditor procedure.
RegisterPropertyEditor takes four parameters:
This is always a call to the built-in function TypeInfo, such as TypeInfo(TMyComponent).
Here is an excerpt from the procedure that registers the editors for the standard components on the Component palette:
procedure Register; begin RegisterPropertyEditor(TypeInfo(TComponent), nil, '', TComponentProperty); RegisterPropertyEditor(TypeInfo(TComponentName), TComponent, 'Name', TComponentNameProperty); RegisterPropertyEditor(TypeInfo(TMenuItem), TMenu, '', TMenuItemProperty); end;
The three statements in this procedure cover the different uses of RegisterPropertyEditor:
Component editors determine what happens when the component is double-clicked in the designer and add commands to the context menu that appears when the component is right-clicked. They can also copy your component to the Windows clipboard in custom formats.
If you do not give your components a component editor, Delphi uses the default component editor. The default component editor is implemented by the class TDefaultEditor. TDefaultEditor does not add any new items to a component's context menu. When the component is double-clicked, TDefaultEditor searches the properties of the component and generates (or navigates to) the first event handler it finds.
To add items to the context menu, change the behavior when the component is double-clicked, or add new clipboard formats, derive a new class from TComponentEditor and register its use with your component. In your overridden methods, you can use the Component property of TComponentEditor to access the component that is being edited.
Adding a custom component editor consists of the steps:
When the user right-clicks the component, the GetVerbCount and GetVerb methods of the component editor are called to build context menu. You can override these methods to add commands (verbs) to the context menu.
Adding items to the context menu requires the steps:
Override the GetVerbCount method to return the number of commands you are adding to the context menu. Override the GetVerb method to return the strings that should be added for each of these commands. When overriding GetVerb, add an ampersand (&) to a string to cause the following character to appear underlined in the context menu and act as a shortcut key for selecting the menu item. Be sure to add an ellipsis (...) to the end of a command if it brings up a dialog. GetVerb has a single parameter that indicates the index of the command.
The following code overrides the GetVerbCount and GetVerb methods to add two commands to the context menu.
function TMyEditor.GetVerbCount: Integer; begin Result := 2; end; function TMyEditor.GetVerb(Index: Integer): String; begin case Index of 0: Result := "&DoThis ..."; 1: Result := "Do&That"; end; end;
Note: Be sure that your GetVerb method returns a value for every possible index indicated by GetVerbCount.
When the command provided by GetVerb is selected in the designer, the ExecuteVerb method is called. For every command you provide in the GetVerb method, implement an action in the ExecuteVerb method. You can access the component that is being edited using the Component property of the editor.
For example, the following ExecuteVerb method implements the commands for the GetVerb method in the previous example.
procedure TMyEditor.ExecuteVerb(Index: Integer);
var
MySpecialDialog: TMyDialog;
begin
case Index of
0: begin
MyDialog := TMySpecialDialog.Create(Application); { instantiate the editor }
if MySpecialDialog.Execute then; { if the user OKs the dialog... }
MyComponent.FThisProperty := MySpecialDialog.ReturnValue; { ...use the value }
MySpecialDialog.Free; { destroy the editor }
end;
1: That; { call the That method }
end;
end;
When the component is double-clicked, the Edit method of the component editor is called. By default, the Edit method executes the first command added to the context menu. Thus, in the previous example, double-clicking the component executes the DoThis command.
While executing the first command is usually a good idea, you may want to change this default behavior. For example, you can provide an alternate behavior if
Override the Edit method to specify a new behavior when the component is double-clicked. For example, the following Edit method brings up a font dialog when the user double-clicks the component:
procedure TMyEditor.Edit; var FontDlg: TFontDialog; begin FontDlg := TFontDialog.Create(Application); try if FontDlg.Execute then MyComponent.FFont.Assign(FontDlg.Font); finally FontDlg.Free end; end;
Note: If you want a double-click on the component to display the Code editor for an event handler, use TDefaultEditor as a base class for your component editor instead of TComponentEditor. Then, instead of overriding the Edit method, override the protected TDefaultEditor.EditProperty method instead. EditProperty scans through the event handlers of the component, and brings up the first one it finds. You can change this to look a particular event instead. For example:
procedure TMyEditor.EditProperty(PropertyEditor: TPropertyEditor; Continue, FreeEditor: Boolean) begin if (PropertyEditor.ClassName = 'TMethodProperty') and (PropertyEditor.GetName = 'OnSpecialEvent') then // DefaultEditor.EditProperty(PropertyEditor, Continue, FreeEditor); end;
By default, when a user chooses Copy while a component is selected in the IDE, the component is copied in Delphi's internal format. It can then be pasted into another form or data module. Your component can copy additional formats to the Clipboard by overriding the Copy method.
For example, the following Copy method allows a TImage component to copy its picture to the Clipboard. This picture is ignored by the Delphi IDE, but can be pasted into other applications.
procedure TMyComponent.Copy; var MyFormat : Word; AData,APalette : THandle; begin TImage(Component).Picture.Bitmap.SaveToClipBoardFormat(MyFormat, AData, APalette); ClipBoard.SetAsHandle(MyFormat, AData); end;
Once the component editor is defined, it can be registered to work with a particular component class. A registered component editor is created for each component of that class when it is selected in the form designer.
To create the association between a component editor and a component class, call RegisterComponentEditor. RegisterComponentEditor takes the name of the component class that uses the editor, and the name of the component editor class that you have defined. For example, the following statement registers a component editor class named TMyEditor to work with all components of type TMyComponent:
RegisterComponentEditor(TMyComponent, TMyEditor);
Place the call to RegisterComponentEditor in the Register procedure where you register your component. For example, if a new component named TMyComponent and its component editor TMyEditor are both implemented in the same unit, the following code registers the component and its association with the component editor.
procedure Register;
begin
RegisterComponents('Miscellaneous', [TMyComponent);
RegisterComponentEditor(classes[0], TMyEditor);
end;
In the Delphi IDE, the Object Inspector affords the programmer the ability to selectively hide and display properties based on property categories. The properties of new custom components can also be fitted into this scheme by registering properties in categories. Do this at the same time the component is being registered by calling one of the property registration functions RegisterPropertyInCategory or RegisterPropertiesInCategory. Use the former to register a single property. Use the latter to register multiple properties in a single function call. These functions are defined in the unit DsgnIntf.
Note that it is not mandatory that you register properties or that you register all of the properties of a custom component when some are registered. Any property not explicitly associated with a category is simply deemed to be in the TMiscellaneousCategory category. These properties would be displayed or hidden in the Object Inspector based on that default categorization.
Delphi supplies thirteen stock property categories, in the form of property classes. Register a property of a new custom component in one of these provided categories or create your on property category classes based on these built-in classes.
In addition to these two functions for registering properties, there is an IsPropertyInCategory function. This function is useful for such endeavors as creating localization utilities, in which you must determine whether a property is registered in a given property category.
Register one property at a time and associate it with a property category using the RegisterPropertyInCategory function. RegisterPropertyInCategory comes in four overloaded variations, each providing a different set of criteria for identifying the property in the custom component to be associated with the property category.
The first variation allows you to identify the property by the property's name. The line below registers a property related to visual display of the component, identifying the property by its name, "AutoSize".
RegisterPropertyInCategory(TVisualCategory, 'AutoSize');
The second variation identifies the property using the characteristics component class type and property name. The example below registers (into the category THelpCategory) a property named "HelpContext" of a component of the custom class TMyButton.
RegisterPropertyInCategory(THelpCategory, TMyButton, 'HelpContext');
The third variation uses the property's type and the property's name to identify the property. The example below registers a property based on a combination of its type, Integer, and its name, "Width".
RegisterPropertyInCategory(TVisualCategory, TypeInfo(Integer), 'Width');
The last variation identifies the property using only its property type. The example below registers a property based on its type, Integer.
RegisterPropertyInCategory(TVisualCategory, TypeInfo(Integer));
See the section Property category classes for a list of the available property categories and a brief description of their uses.
Register multiple properties at one time and associate them with a property category using the RegisterPropertiesInCategory function. RegisterPropertiesInCategory comes in three overloaded variations, each providing a different set of criteria for identifying the property in the custom component to be associated with property categories.
The first variation allows you to identify properties for association with a property category based on property name. A list of property names is passed as an array of String. Each property identified by name in the list is registered with the specified property category. In the example below, four properties are registered in the category THelpCategory. These four properties are identified by name using the Strings "HelpContext", "Hint", "ParentShowHint", and "ShowHint".
RegisterPropertiesInCategory(THelpCategory, ['HelpContext', 'Hint', 'ParentShowHint', 'ShowHint']);
The second variation identifies the properties by their type. In the example below, all of the properties in the custom component that are of type String are registered in the category TLocalizableCategory.
RegisterPropertiesInCategory(TLocalizableCategory, TypeInfo(String));
The third variation allows you to pass a list of various criteria, not all of which need be the same type, to use to identify properties to register. The list is passed as an array of constants. In the example below, any property that either has the name "Text" or belongs to a class of type TEdit is registered in the category TLocalizableCategory.
RegisterPropertiesInCategory(TLocalizableCategory, ['Text', TEdit]);
See the section Property category classes for a list of the available property categories and a brief description of their uses.
Delphi provides a built-in set of twelve property categories with which you can associate properties in custom components. Use one of these property category class names for the ACategoryClass parameter of the RegisterPropertyInCategory and RegisterPropertiesInCategory functions.:
You can create new property categories of your own design by deriving a class from either the base class TPropertyCategory or one of the built-in descendants. See the section Property category classes for a list of the available property categories and a brief description of their uses.
When deriving a new property category class, override the Name method. The Name method provides the name of the category for display in the Object Inspector. This method must be superseded with a method that returns the name of the custom category. The Name method may simply return a String value or it may retrieve a value from a resource. The latter is useful for easily internationalizing a custom component and its categories.
Given the new Name method below for a custom category class, the text "My Special" would appear in the Object Inspector when property categories are displayed (and at least one of the current object's properties is registered in this property class).
class function TMySpecialCategory.Name: String; begin Result := 'My Special'; end;
An application can query the existing registered properties to determine whether a given property is already registered in a specified category. This can be especially useful in situations like a localization utility that checks the categorization of properties preparatory to performing its localization operations. Two overloaded variations of the IsPropertyInCategory function are available, allowing for different criteria in determining whether a property is in a category.
The first variation allows you to base the comparison criteria on a combination of the class type of the owning component and the property's name. In the command line below, for IsPropertyInCategory to return True, the property must belong to the class TEdit, have the name "Text", and be in the property category TLocalizableCategory.
IsItThere := IsPropertyInCategory(TLocalizableCategory, TEdit, 'Text');
The second variation allows you to base the comparison criteria on a combination of the class name of the owning component and the property's name. In the command line below, for IsPropertyInCategory to return True, the property must belong to the class TEdit, have the name "Text", and be in the property category TLocalizableCategory.
IsItThere := IsPropertyInCategory(TLocalizableCategory, 'TEdit', 'Text');
Once your components are registered, you must compile them as packages before they can be installed in the IDE. A package can contain one or several components as well as custom property editors. For more information about packages, see "Working with packages and components".
To create and compile a package, see "Creating and editing packages". Put the source-code units for your custom components in the package's Contains list. If your components depend on other packages, include those packages in the Requires list.
To install your components in the IDE, see "Installing component packages".
pubsweb@inprise.com
Copyright © 1999, Inprise Corporation. All rights reserved.