Delphi

Why create properties?

Properties provide significant advantages, both for you as a component writer and for the users of your components. The most obvious advantage is that properties can appear in the Object Inspector at design time. That simplifies your programming job, because instead of handling several parameters to construct an object, you just read the values assigned by the user.

From the component user's standpoint, properties look like variables. Users can set or read the values of properties much as if those properties were object fields. About the only thing they cannot do with a property that they would with a variable is pass it as a var parameter.

From the component writer's standpoint, however, properties provide much more power than simple object fields because

Users can set properties at design time.

This is very important, because unlike methods, which are only available at run time, properties let users customize components before running an application. In general, your components should not contain a lot of methods; most of them can probably be encapsulated into properties.

Unlike an object field, a property can hide implementation details from users.

For example, the data might be stored internally in an encrypted form, but when setting or reading the value of the property, the data would appear unencrypted. Although the value of a property might be a simple number, the component might look up the value from a database or perform complex calculations to arrive at that value.

Properties allow side effects to outwardly simple assignments.

What appears to be a simple assignment involving an object field is really a call to a method, and that method could do almost anything.

A simple but clear example is the Top property of all components. Assigning a new value to Top doesn't just change some stored value; it causes the component to relocate and repaint itself. The effects of property setting need not be limited to an individual component. Setting the Down property of a speed-button component to True causes the speed button to set the Down properties of all other speed buttons in its group to False.

The implementation methods for a property can be virtual, meaning that what looks like a single property to a component user might do different things in different components.

 

Type of Property

A property can be of any type that a function can return (since the implementation of the property can use a function). All the standard rules of Pascal type compatibility apply to properties, just as they would to variables. Type compatibility is explained in Chapter 4 of the Delphi User's Guide.

The most important aspect of choosing types for your properties is that different types appear differently in the Object Inspector. The Object Inspector uses the type of the property to determine what choices appear to the user. You can specify a different property editor when you register your components, as explained in Writing property editors.

Property type     Object Inspector treatment

Simple               Numeric, character, and string properties appear in the Object Inspector as numbers, characters, and strings, respectively. The user can type and edit the value of the property directly.

Enumerated Properties of enumerated types (including Boolean) display the value as defined in the source code. The user can cycle through the possible values by double-clicking the value column. There is also a drop-down list that shows all possible values of the enumerated type.

Set                      Properties of set types appear in the Object Inspector looking like a set. By expanding the set, the user can treat each element of the set as a Boolean value: True if the element is included in the set or False if it's not included.

Object              Properties that are themselves objects often have their own property editors. However, if the object that is a property also has published properties, the Object Inspector allows the user to expand the list of object properties and edit them individually. Object properties must descend from TPersistent.

Array                Array properties must have their own property editors. The Object Inspector has no built-in support for editing array properties.

 

Publishing inherited properties

All components inherit properties from their ancestor types. When you derive a new component from an existing component type, your new component inherits all the properties in the ancestor type. If you derive instead from one of the abstract types, many of the inherited properties are either protected or public, but not published.

If you need more information about levels of protection such as protected, private, and published, see Controlling access.

To make a protected or public property available to users of your components, you must redeclare the property as published.

Redeclaring means adding the declaration of an inherited property to the declaration of a descendant object type.

 

The property declaration

Declaring a property and its implementation is straightforward. You add the property declaration to the declaration of your component object type.

To declare a property, you specify three things:

The name of the property

The type of the property

Methods to read and/or set the value of the property

At a minimum, a component's properties should be declared in a public part of the component's object-type declaration, making it easy to set and read the properties from outside the component at run time.

To make the property editable at design time, declare the property in a published part of the component's object type declaration. Published properties automatically appear in the Object Inspector. Public properties that aren't published are available only at run time.

 

Internal data storage

There are no restrictions on how you store the data for a property. In general, however, Delphi's components follow these conventions:

Property data is stored in object fields.

Identifiers for properties' object fields start with the letter F, and incorporate the name of the property. For example, the raw data for the Width property defined in TControl is stored in an object field called FWidth.

Object fields for property data should be declared as private. This ensures that the component that declares the property has access to them, but component users and descendant components don't.

Descendant components should use the inherited property itself, not direct access to the internal data storage, to manipulate a property.

The underlying principle behind these conventions is that only the implementation methods for a property should access the data behind that property. If a method or another property needs to change that data, it should do so through the property, not by direct access to the stored data. This ensures that the implementation of an inherited property can change without invalidating descendant components.

 

Direct access

The simplest way to make property data available is direct access. That is, the read and write parts of the property declaration specify that assigning or reading the property value goes directly to the internal storage field without calling an access method. Direct access is useful when the property has no side effects, but you want to make it available in the Object Inspector.

It is common to have direct access for the read part of a property declaration but use an access method for the write part, usually to update the status of the component based on the new property value.

For example:

type

  TSampleComponent = class(TComponent)

  private  { internal storage is private }

    FReadOnly: Boolean;          { declare field to hold property value }

  published       { make property available at design time }

    property ReadOnly: Boolean read FReadOnly write FReadOnly;

  end;

 

Access methods

The syntax for property declarations allows the read and write parts of a property declaration to specify access methods instead of an object field. Regardless of how a particular property implements its read and write parts, however, that implementation should be private, and descendant components should use the inherited property for access. This ensures that use of a property will not be affected by changes in the underlying implementation.

 

Making access methods private also ensures that component users don't accidentally call those methods, inadvertently modifying a property.

The read method

The read method for a property is a function that takes no parameters, and returns a value of the same type as the property. By convention, the function's name is "Get" followed by the name of the property. For example, the read method for a property named Count would be named GetCount.

The only exception to the "no parameters" rule is for array properties, which pass their indexes as parameters.

The read method manipulates the internal storage data as needed to produce the value of the property in the appropriate type.

If you don't declare a read method, the property is write-only. Write-only properties are very rare, and generally not very useful.

The write method

The write method for a property is always a procedure that takes a single parameter, of the same type as the property. The parameter can be passed by reference or by value, and can have any name you choose. By convention, the procedure's name is "Set" followed by the name of the property. For example, the write method for a property named Count would be named SetCount.

The value passed in the parameter is used to set the new value of the property, so the write method needs to perform any manipulation needed to put the appropriate values in the internal storage.

If you don't declare a write method, the property is read-only.

It's common to test whether the new value actually differs from the current value before setting the value. For example, here's a simple write method for an integer property called Count that stores its current value in a field called FCount:

procedure TMyComponent.SetCount(Value: Integer);

begin

  if Value <> FCount then

  begin

    FCount := Value;

    Update;

  end;

end;

 

Default property values

When you declare a property, you can optionally declare a default value for the property. The default value for a component's property is the value set for that property in the component's constructor. For example, when you place a component from the Component palette on a form, Delphi creates the component by calling the component's constructor, which determines the initial values of the component's properties.

Delphi uses the declared default value to determine whether to store a property in a form file. For more information on storing properties and the importance of default values, see Storing and loading properties. If you do not specify a default value for a property, Delphi always stores the property.

To declare a default value for a property, append the default directive to the property's declaration (or redeclaration), followed by the default value.

Note:         Declaring a default value in the property declaration does not actually set the property to that value. It is your responsibility as the component writer to ensure that the component's constructor actually sets the property to that value.

Specifying no default value

When redeclaring a property, you can specify that the property has no default value, even if the inherited property specified one.

To designate a property as having no default value, append the nodefault directive to the property's declaration.

When you declare a property for the first time, there is no need to specify nodefault, because the absence of a declared default value means the same thing.

Example

Here is the declaration of a component that includes a single Boolean property named IsTrue with a default value of True, including the constructor that sets the default value.

type

  TSampleComponent = class(TComponent)

  private

    FIsTrue: Boolean;

  public

    constructor Create(AOwner: TComponent); override;

  published

    property IsTrue: Boolean read FIsTrue write FIsTrue default True;

  end;

...

constructor TSampleComponent.Create(AOwner: TComponent);

begin

  inherited Create(AOwner);  { always call the inherited constructor }

 

  FIsTrue := True;   { set the default value }

end;

 

Note that if the default value for IsTrue had been False, you would not need to set it explicitly in the constructor, since all objects (and therefore, components) always initialize all their fields to zero, and a "zeroed" Boolean value is False.

 

Creating array properties

Some properties lend themselves to being indexed, much like arrays. That is, they have multiple values that correspond to some kind of index value. An example in the standard components is the Lines property of the memo component. Lines is an indexed list of the strings that make up the text of the memo, which you can treat as an array of strings. In this case, the array property gives the user natural access to a particular element (a string) in a larger set of data (the memo text).

Array properties work just like other properties, and you declare them in largely the same way. The only differences in declaring array properties are as follows:

The declaration for the property includes one or more indexes with specified types. Indexes can be of any type.

The read and write parts of the property declaration, if specified, must be methods. They cannot be object fields.

The access methods for reading and writing the property values take additional parameters that correspond to the index or indexes. The parameters must be in the same order and of the same type as the indexes specified in the property declaration.

Although they seem quite similar, there are a few important distinctions between array properties and arrays. Unlike the index of an array, the index type for an array property does not have to be an integer type. You can index a property on a string, for example. In addition, you can only reference individual elements of an array property, not the entire range of the property.

Here's the declaration of a property that returns a string based on an integer index:

Example:

type

  TDemoComponent = class(TComponent)

  private

    function GetNumberName(Index: Integer): string;

  public

    property NumberName[Index: Integer]: string read GetNumberName;

  end;

...

function TDemoComponent.GetNumberName(Index: Integer): string;

begin

  Result := 'Unknown';

  case Index of

    -MaxInt..-1: Result := 'Negative';

    0: Result := 'Zero';

 

    1..100: Result := 'Small';

    101..MaxInt: Result := 'Large';

  end;

end;

 

Writing property editors

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 five steps:

Deriving a property-editor object

Editing the property as text

Editing the property as a whole

Specifying editor attributes

Registering the property editor

 

Deriving a property-editor object

The DsgnIntf unit defines several kinds of property editors, all of which descend from TPropertyEditor. When you create a property editor, your property-editor object can either descend directly from TPropertyEditor or indirectly through one of the property-editor types described in the table below.

To create a property-editor object, derive a new object type from one of the existing property editor types.

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 more useful for user-defined properties.

 

Type                     Properties edited

TOrdinalProperty              All ordinal-property editors (those for integer, character, and enumerated properties) descend from TOrdinalProperty.

TIntegerProperty             All integer types, including predefined and user-defined subranges.

TCharProperty                  Char-type and subranges of Char, such as 'A'..'Z'.

TEnumProperty               Any enumerated type.

TFloatProperty                 All floating-point numbers.

TStringProperty               Strings, including strings of specified length, such as string[20]

TSetElementProperty   Individual elements in sets, shown as Boolean values

TSetProperty                    All sets. Sets are not directly editable, but can expand into a list of set-element properties.

TClassProperty                Objects. Displays the name of the object type and allows expansion of the object's properties.

TMethodProperty            Method pointers, most notably events.

TComponentProperty     Components in the same form. The user cannot edit the component's properties, but can point to a specific component of a compatible type.

TColorProperty                 Component colors. Shows color constants if applicable, otherwise displays hexadecimal value. Drop-down list contains the color constants. Double-click opens the color-selection dialog box.

TFontNameProperty      Font names. The drop-down list displays all currently installed fonts.

TFontProperty                  Fonts. Allows expansion of individual font properties as well as access to the font dialog box.

 

Example

One of the simplest property editors is TFloatPropertyEditor, the editor for properties that are floating-point numbers. Here is its declaration:

type

  TFloatProperty = class(TPropertyEditor)

  public

    function AllEqual: Boolean; override;

    function GetValue: string; override;

    procedure SetValue(const Value: string); override;

  end;

 

Editing the property as text

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 objects 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 the following table

Property type       "Get" method     "Set" method

Floating point                        GetFloatValue                 SetFloatValue

Method pointer (event)      GetMethodValue            SetMethodValue

Ordinal type                            GetOrdValue                    SetOrdValue

String                                       GetStrValue                     SetStrValue.

 

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.

Displaying the property value

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 isn't a string value, your GetValue must convert the value into a string representation.

Setting the property value

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 raise an exception and not use the improper value.

To read string values into properties, override the property editor's SetValue method.

Examples

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.

 

Editing the property as a whole

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 objects. 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 object's Edit method.

Note that 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. Since 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.

Example

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;

 

Specifying editor attributes

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 function that returns a set of values of type TPropertyAttributes that can include any or all of the following values:

 

Flag                     Meaning if included                                                                                      Related method

paValueList              The editor can give a list of enumerated values.                                                                 GetValues

paSubProperties    The property has subproperties that can display.                                                              GetProperties

paDialog                     The editor can display a dialog box for editing the entire property.                               Edit

paMultiSelect           The property should display when the user selects more than one component.     N/A

paAutoUpdate         Updates the component after every change instead of waiting for approval of the value.       SetValue

paSortList                The Object Inspector should sort the value list.                                                                 N/A

paReadOnly              Users cannot modify the property value.                                                                              N/A

 

Example

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;

 

Registering the property editor

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:

A type-information pointer for the type of property to edit.

This is always a call to the built-in function TypeInfo, such as TypeInfo(TMyComponent).

The type of the component to which this editor applies. If this parameter is nil, the editor applies to all properties of the given type.

The name of the property. This parameter only has meaning if the previous parameter specifies a particular type of component. In that case, you can specify the name of a particular property in that component type to which this editor applies.

The type of property editor to use for editing the specified property.

Example

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:

 

The first statement is the most typical. It registers the property editor TComponentProperty for all properties of type TComponent (or descendants of TComponent that do not have their own editors registered). In general, when you register a property editor, you've created an editor for a particular type, and you want to use it for all properties of that type, so the second and third parameters are nil and an empty string, respectively.

The second statement is the most specific kind of registration. It registers an editor for a specific property in a specific type of component. In this case, the editor is for the Name property of all components.

The third statement is more specific than the first, but not as limited as the second. It registers an editor for all properties of type TMenuItem in components of type TMenu.

 

Creating events

Events are very important parts of components, although the component writer doesn't usually need to do much with them. An event is a link between an occurrence in the system (such as a user action or a change in focus) that a component might need to respond to and a piece of code that responds to that occurrence. The responding code is an event handler, and is nearly always written by the component user.

By using events, application developers can customize the behavior of components without having to change the objects themselves. As a component writer, you use events to enable application developers to customize the behavior of your components.

Events for the most common user actions (such as mouse actions) are built into all the standard Delphi components, but you can also define new events and give them their own events. In order to create events in a component, you need to understand the following:

 

What are events?

Implementing the standard events

Defining your own events

 

Delphi implements events as properties, so you should already be familiar with Creating properties before you attempt to create or change a component's events.

 

What are events?

Loosely defined, an event is a mechanism that links an occurrence to some code. More specifically, an event is a method pointer, a pointer to a specific method in a specific object instance.

From the component user's perspective, an event is just a name related to a system event, such as OnClick, that the user can assign a specific method to call. For example, a push button called Button1 has an OnClick method. By default, Delphi generates an event handler called Button1Click in the form that contains the button and assigns it to OnClick. When a click event occurs on the button, the button calls the method assigned to OnClick, in this case, Button1Click.

 

Thus, the component user sees the event as a way of specifying what user-written code the application should call when a specific event occurs.

From the component writer's perspective, there's a lot more to an event. The most important thing to remember, however, is that you're providing a link, a place where the component's user can attach code in response to certain kinds of occurrences. Your components provide outlets where the user can "plug in" specific code.

In order to write an event, you need to understand the following:

 

Events are method pointers

Events are properties

Event-handler types

Event handlers are optional

 

Events are method pointers

Delphi uses method pointers to implement events. A method pointer is a special pointer type that points to a specific method in a specific object instance. As a component writer, you can treat the method pointer as a placeholder: your code detects that an event occurs, so you call the method (if any) specified by the user for that event.

Method pointers work just like any other procedural type, but they maintain a hidden pointer to an object instance. When the user assigns a handler to a component's event, the assignment is not just to a method with a particular name, but rather to a specific method of a specific object instance. That instance is usually the form that contains the component, but it need not be.

Example

All controls inherit a dynamic method called Click for handling click events. The implementation of Click calls the user's click-event handler, if any:

procedure TControl.Click;

begin

  if Assigned(OnClick) then OnClick(Self);

end;

If the user has assigned a handler to a control's OnClick event, clicking the control results in that method being called. If no handler is assigned, nothing happens.

 

Events are properties

Components use properties to implement their events. Unlike most other properties, events don't use methods to implement their read and write parts. Instead, event properties use a private object field of the same type as the property.

By convention, the field's name is the same as the name of the property, but preceded by the letter F. For example, the OnClick method's pointer is stored in a field called FOnClick of type TNotifyEvent, and the declaration of the OnClick event property looks like this:

type

  TControl = class(TComponent)

  private

    FOnClick: TNotifyEvent;   { declare a field to hold the method pointer }

    ...

  protected

    property OnClick: TNotifyEvent read FOnClick write FOnClick;

  end;

To learn about TNotifyEvent and other event types, see Event-handler types.

As with any other property, you can set or change the value of an event at run time. The main advantage to having events be properties, however, is that component users can assign handlers to events at design time, using the Object Inspector.

 

Event-handler types

Because an event is a pointer to an event handler, the type of the event property must be a method pointer type. Similarly, any code to be used as an event handler must be an appropriately typed method of an object.

All event-handler methods are procedures. To be compatible with an event of a given type, an event-handler method must have the same number and type of parameters, in the same order, passed in the same way.

Delphi defines method types for all its standard events. When you create your own events, you can use an existing type if that's appropriate, or define one of your own.

Event-handler types are procedures

Although the compiler allows you to declare method-pointer types that are functions, you should never do so for handling events. Because an empty function returns an undefined result, an empty event handler that was a function might not always be valid. For this reason, all your events and their associated event handlers must be procedures.

Although an event handler cannot be a function, you can still get information back from the user's code using var parameters. When doing this, make sure you assign a valid value to the parameter before calling the handler so you don't require the user's code to change the value.

An example of passing var parameters to an event handler is the key-pressed event, of type TKeyPressEvent. TKeyPressEvent defines two parameters, one to indicate which object generated the event, and one to indicate which key was pressed:

type

  TKeyPressEvent = procedure(Sender: TObject; var Key: Char) of object;

Normally, the Key parameter contains the character pressed by the user. Under certain circumstances, however, the user of the component might want to change the character. One example might be to force all characters to uppercase in an editor. In that case, the user could define the following handler for keystrokes:

procedure TForm1.Edit1KeyPressed(Sender: TObject; var Key: Char);

begin

  Key := UpCase(Key);

end;

You can also use var parameters to let the user override the default handling.

 

Event handlers are optional

The most important thing to remember when creating events for your components is that users of your components might not attach handlers to the events. That means that your component shouldn't fail or generate errors simply because a user of the component failed to attach a handler to a particular event.

The mechanics of calling handlers and dealing with events that have no attached handler are explained in Calling the event, but the principle has important implications for the design of your components and their events.

The optional nature of event handlers has two aspects:

Component users do not have to handle events.

Events happen almost constantly in a Windows application. Just by moving the mouse pointer across a component you cause Windows to send numerous mouse-move messages to the component, which the component translates into OnMouseMove events. In most cases, however, users of components don't care to handle the mouse move events, and this does not cause a problem. The component does not depend on the mouse events being handled.

Similarly, the components you create should not be dependent on users handling the events they generate.

Component users can write any code they want in an event handler.

In general, there are no restrictions on the code a user can write in an event handler. The components in the Delphi component library all have events written in such a way that they minimize the chances of user-written code generating unexpected errors. Obviously, you can't protect against logic errors in user code, but you can, for example, ensure that all data structures are initialized before calling events so that users don't try to access invalid information.

 

Implementing the standard events

All the controls that come with Delphi inherit events for all of the most common Windows events. Collectively, these are called the standard events. Although all these events are built into the standard controls, by default they are protected, meaning end users can't attach handlers to them. When you create a control, you can choose to make events visible to users of your control.

There are three things you need to consider when incorporating the standard events into your controls:

 

What are the standard events?

Making events visible

Changing the standard event handling

 

What are the standard events?

There are two categories of standard events: those defined for all controls and those defined only for the standard windows controls.

Standard events for all controls

The most basic events are defined in the object type TControl. All controls, whether windowed or graphical or custom, inherit these events. The following table lists all the events available in all controls:

OnClick      OnDragDrop    OnEndDrag     OnMouseMove

OnDblClick         OnDragOver    OnMouseDown         OnMouseUp

All the standard events have corresponding protected dynamic methods declared in TControl, with names that correspond to the event names, but without the preceding "On." For example, OnClick events call a method named Click.

Standard events for standard controls

In addition to the events common to all controls, standard controls (those descended from TWinControl) have the following events:

OnEnter   OnKeyDown     OnKeyPress    OnKeyUp OnExit    

As with the standard events in TControl, the windowed-control events have corresponding methods.

 

Making events visible

The declarations of the standard events are protected, as are the methods that correspond to them. If you want to make those events accessible to users either at run time or design time, you need to redeclare the event property as either public or published.

Redeclaring a property without specifying its implementation keeps the same implementation methods, but changes the protection level. Thus, you can take an event that's defined in the standard TControl but not made visible to users and promote it to a level that the user can see and use it.

Example

If you create a component that needs to surface the OnClick event at design time, you add the following to the component's type declaration:

type

  TMyControl = class(TCustomControl)

    ...

  published

    property OnClick;       { makes OnClick available in Object Inspector }

  end;

(I made a BUG here in the first version my component expert.:-( )

 

Changing the standard event handling

If you want to change the way your custom component responds to a certain kind of event, you might be tempted to write some code and assign it to the event. As a component user, that's exactly what you would do. However, when you're creating components you can't do that, because you need to keep the event available for the users of the component.

This is precisely the reason for the protected implementation methods associated with each of the standard events. By overriding the implementation method, you can modify the internal event handling, and by calling the inherited method you can maintain the standard handling, including the event for the user's code.

The order in which you call the inherited method is significant. As a general rule, you call the inherited method first, allowing the user's event-handler code to execute before your customizations (and in some cases, to keep from executing the customizations). However, there might be times when you want to execute your code before calling the inherited method. For example, if the inherited code is somehow dependent on the status of the component and your code changes that status, you should make the changes and then allow the user's code to respond to the changed status.

Example

Suppose you're writing a component and you want to modify the way your new component responds to clicks. Instead of assigning a handler to the OnClick event as a component user would do, you override the protected method Click:

procedure TMyControl.Click;

begin

  inherited Click;       { perform standard handling, including calling handler }

  { your customizations go here }

end;

 

Triggering the event

The first issue you encounter when defining your own events that you don't need to consider when using the standard events is what triggers the event. For some events, the answer is obvious. For example, a mouse-down event occurs when the user presses the left button on the mouse and Windows sends a WM_LBUTTONDOWN message to the application. Upon receiving that message, a component calls its MouseDown method, which in turn calls any code the user has attached to the OnMouseDown event.

But some events are less clearly tied to specific external events. A scroll bar, for example, has an OnChange event, triggered by numerous kinds of occurrences, including keystrokes, mouse clicks, or changes in other controls. When defining your events, you must ensure that all the appropriate occurrences call the proper events.

Example

Here are the methods TControl uses to handle the WM_LBUTTONDOWN message from Windows. DoMouseDown is a private implementation method that provides generic handling for left, right, and middle button clicks, translating the parameters of the Windows message into values for the MouseDown method.

type

  TControl = class(TComponent)

  private

    FOnMouseDown: TMouseEvent;

    procedure DoMouseDown(var Message: TWMMouse; Button: TMouseButton; Shift: TShiftState);

    procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;

  protected

    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;

  end;

...

procedure TControl.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);

begin

  if Assigned(FOnMouseDown) then

    FOnMouseDown(Self, Button, Shift, X, Y);       { call handler, if any }

end;

 

procedure TControl.DoMouseDown(var Message: TWMMouse; Button: TMouseButton;

  Shift: TShiftState);

begin

  with Message do

    MouseDown(Button, KeysToShiftState(Keys) + Shift, XPos, YPos);    { call dynamic method }

 

end;

 

procedure TControl.WMLButtonDown(var Message: TWMLButtonDown);

begin

  inherited;       { perform default handling }

  if csCaptureMouse in ControlStyle then MouseCapture := True;

  if csClickEvents in ControlStyle then Include(FControlState, csClicked);

  DoMouseDown(Message, mbLeft, []);         { call the generic mouse-down method }

end;

 

Two kinds of events

There are two kinds of occurrences you might need to provide events for: state changes and user interactions. The mechanics of handling them are the same, but the semantics are slightly different.

User-interaction events will nearly always be triggered by a message from Windows, indicating that the user did something your component might need to respond to. State-change events might also be related to messages from Windows (focus changes or being enabled, for example), but they can also occur through changes in properties or other code. You have total control over the triggering of events you define. You should be consistent and complete so that users of your components know how to use the events.

 

Defining the handler type

Once you determine that your event occurred, you have to define how you want the event handled. That means determining the type of the event handler. In most cases, handlers for the events you define yourself will be either simple notifications or event-specific types. It's also possible to get information back from the handler.

Simple notifications

A notification event is one that only tells you that the particular event happened, with no specific information about when or where. Notifications use the type TNotifyEvent, which carries only one parameter, the sender of the event. Thus, all a handler for a notification "knows" about the event is what kind of event it was, and what component the event happened to. For example, click events are notifications. When you write a handler for a click event, all you know is that a click occurred and which component was clicked.

Notification is a one-way process. There is no mechanism to provide feedback or prevent further handling of a notification.

Event-specific handlers

In some cases, it's not enough to know just what event happened and what component it happened to. For example, if the event is a key-press event, it's likely that the handler will want to know which key the user pressed. In these cases, you need handler types that include parameters with any necessary information about the event.

If your event was generated in response to a message, it's likely that the parameters you pass to the event handler come directly from the message parameters.

Returning information from the handler

Because all event handlers are procedures, the only way to pass information back from a handler is through a var parameter. Your components can use such information to determine how or whether to process an event after the user's handler executes.

For example, all the key events (OnKeyDown, OnKeyUp, and OnKeyPress) pass the value of the key pressed in a var parameter named Key. The event handler can change Key so that the application sees a different key as being involved in the event. This is a way to, for instance, force typed characters to uppercase.

 

Declaring the event

Once you've determined the type of your event handler, you're ready to declare the method pointer and the property for the event. Be sure to give the event a meaningful and descriptive name so that users can understand what the event does. Try to be consistent with names of similar properties in other components.

Event names start with "On"

The names of all the standard events in Delphi begin with "On." This is only a convention; the compiler doesn't enforce it. The Object Inspector determines that a property is an event by looking at the type of the property: all method-pointer properties are assumed to be events and appear on the Events page.

Component users expect to find events in the alphabetical list of names starting with "On." Using other kinds of names will likely confuse them.

 

Calling the event

In general, it's best to centralize calls to an event. That is, create a virtual method in your component that calls the user's event handler (if the user assigns one) and provides any default handling.

Putting all the event calls in one place ensures that someone deriving a new component from your component can customize event handling by overriding that one method, rather than searching through your code looking for places you call the event.

There are two other considerations when calling the event:

 

Empty handlers must be valid

Users can override default handling

 

Empty handlers must be valid

ou should never create a situation in which an empty event handler causes an error. That is, the proper functioning of your component should not depend on a particular response from the user's event-handler code. In fact, an empty handler should produce the same result as no handler at all.

Components should never require the user to use them in a particular way. An important aspect of that principle is that component users expect no restrictions on what they can do in an event handler.

Example

Since an empty handler should behave the same as no handler, the code for calling the user's handler should look like this:

if Assigned(OnClick) then OnClick(Self);

{ perform default handling }

You should never have something like this:

if Assigned(OnClick) then OnClick(Self)

else { perform default handling };

 

Users can override default handling

For some kinds of events, the user might want to replace the default handling or even suppress all responses. To enable users to do that, you need to pass a var parameter to the handler and check for a certain value when the handler returns.

Note that this is in keeping with the notion that empty handlers should have the same effect as no handler at all: since an empty handler won't change the values of any var parameters, the default handling always takes place after calling the empty handler.

Example

When handling key-press events the user can suppress the component's default handling of the keystroke by setting the var parameter Key to a null character (#0). The logic for supporting that looks like this:

if Assigned(OnKeyPress) then OnKeyPress(Self, Key);

if Key <> #0 then    { perform default handling };

The actual code is a little different from this because it's dealing with Windows messages, but the logic is the same. By default, the component calls any user-assigned handler, then performs its standard handling. If the user's handler sets Key to a null character, the component skips the default handling.

 

Creating methods

Component methods are no different from any other object's methods. That is, they are just procedures and functions built into the structure of a component object. Although there are essentially no restrictions on what you can do with the methods of a component, Delphi does use some standards you should follow.

The guidelines to follow when writing methods for your components include

Avoiding interdependencies

Naming methods

Protecting methods

Making methods virtual

Declaring methods

As a general rule, minimize the number of methods users need to call to use your components. A lot of the features you might be inclined to implement as methods are probably better encapsulated into properties. Doing so provides an interface that suits the Delphi environment, and also lets users access them at design time.

 

Avoiding interdependencies

At all times when writing components, minimize the preconditions imposed on the component user. To the greatest extent possible, component users should able to do anything they want to a component, whenever they want to do it. There will be times when you can't accommodate that, but your goal should be to come as close as possible.

Although it's impossible to list all the possible kinds of interdependencies you want to avoid, this list gives you an idea of the kinds of things to avoid:

Methods that the user must call in order to use the component

Methods that need to execute in a particular order

Methods that put the component into a state or mode where certain events or methods could be invalid

The best way to handle these situations is to ensure that you provide ways out of them. For example, if calling a method puts your component into a state where calling another method might be invalid, then write that other method in such a way that if the user calls it when the component is in a bad state, the method corrects that state before executing its main code. At a minimum, you should raise an exception in cases when a user calls a method that is invalid.

In other words, if you create a situation where parts of your code depend on each other, the burden should be on you to be sure that using the code in incorrect ways does not cause the user problems. A warning message, for example, is preferable to crashing if the user doesn't accommodate your interdependencies.

 

Naming methods

Delphi imposes no restrictions on what you name methods or their parameters. However, there are a few conventions that make methods easier for users of your components. Keep in mind that the nature of a component architecture dictates that many different kinds of people might use your components.

If you're accustomed to writing code that only you or a small group of programmers uses, you might not think too much about how you name things. It is a good idea to make your method names clear because people unfamiliar with your code (and even unfamiliar with coding) might have to use your components.

Here are some suggestions for making clear method names:

 

Make names descriptive.

A name like PasteFromClipboard is much more informative than simply Paste or PFC.

Procedure names should be active.

Use active verbs in your procedure names. For example, ReadFileNames is much more helpful than DoFiles.

Function names should reflect the nature of what they return.

Although it might be obvious to you as a programmer that a function named X returns the horizontal position of something, a name like GetHorizontalPosition is more universally understandable.

As a final consideration, make sure the method really needs to be a method. A good guideline is that method names have verbs in them. If you find that you create a lot of methods that do not have verbs in their names, consider whether those methods ought to be properties.

 

Protecting methods

All parts of objects, including fields, methods, and properties, can have various levels of protection, as explained in Controlling access. Choosing the appropriate level of protection for methods is quite straightforward.

As a general rule, methods you write in your components will be either public or protected. The exception to that rule is methods that implement properties, which should always be private. Otherwise, you rarely need to make a method private, unless it is truly specific to that particular type of component, to the point that even components derived from it should not have access to it.

Note:         There is generally no reason for declaring a method (other than an event handler) as published. Doing so looks to the end user exactly as if the method were public.

Methods that should be public

Any methods that users of your components need to be able to call must be declared as public. Keep in mind that most method calls occur in event handlers, so methods should avoid unduly tying up system resources or putting Windows in a state where it can't respond to the user.

Note:         Constructors and destructors should always be public.

Methods that should be protected

Any methods that are implementation methods for the component should be protected. That keeps users from calling them at the wrong time. If you have methods that a user's code should not call, but which descendant objects will need to call, you should declare the methods as protected.

For example, suppose you have a method that relies on having certain data set up for it beforehand. If you make that method public, there's a chance a user will call it before setting up the data. On the other hand, by making it protected, you ensure that the user can't call it directly. You can then set up other, public methods that ensure that data setup occurs before calling the protected method.

Methods that should be private

The one category of methods that should always be private is property-implementation methods. You definitely don't want users calling methods that manipulate the data for a property. They should only access that information by accessing the property itself. You can ensure that by making the property public and its implementation methods private.

If descendant objects need to override the implementation of a method, they can do so, but instead of overriding the implementation methods, they must access the inherited property value through the property itself.

 

Making methods virtual

Virtual methods in Delphi components are no different from virtual methods in other objects. You make methods virtual when you want different types to be able to execute different code in response to the same method call.

If you create components intended to be used directly by end users, you can probably make all your methods static. On the other hand, if you create components of a more abstract nature, which other component writers will use as the starting point for their own components, consider making the added methods virtual. That way, components derived from your components can override the inherited virtual methods.

 

Declaring methods

Declaring a method in a component is no different from declaring any object method.

To declare a new method in a component, you do two things:

Add the declaration to the component's object-type declaration

Implement the method in the implementation part of the component's unit

Example

The following code shows a component that defines two new methods: one protected static method and one public virtual method.

type

  TSampleComponent = class(TControl)

  protected

    procedure MakeBigger;    { declare protected static method }

  public

    function CalculateArea: Integer; virtual;          { declare public virtual method }

  end;

...

implementation

...

procedure TSampleComponent.MakeBigger;         { implement first method }

begin

  Height := Height + 5;

  Width := Width + 5;

end;

 

function TSampleComponent.CalculateArea: Integer; { implement second method }

begin

  Result := Width * Height;

end;

 

Using graphics in components

Windows provides a powerful Graphics Device Interface (GDI) for drawing device-independent graphics. Unfortunately, GDI imposes a lot of extra requirements on the programmer, such as managing graphic resources. As a result, you spend a lot of time doing things other than what you really want to do: creating graphics.

Delphi takes care of all the GDI drudgery for you, allowing you to spend your time doing productive work instead of searching for lost handles or unreleased resources. Delphi tackles the tedious tasks so you can focus on the productive and creative ones.

Note that, as with any part of the Windows API, you can call GDI functions directly from your Delphi application if you want to. However, you will probably find that using Delphi's encapsulation of the graphic functions is a much more productive way to create graphics.

There are several important topics dealing with graphics in Delphi:

Overview of Delphi graphics

Delphi encapsulates the Windows GDI at several levels. The most important to you as a component writer is the way components display their images on the screen. When calling GDI functions directly, you need to have a handle to a device context, into which you have selected various drawing tools such as pens and brushes and fonts. After rendering your graphic images, you must then restore the device context to its original state before disposing of it.

Instead of forcing you to deal with graphics at that detailed level, Delphi provides a simple yet complete interface: your component's Canvas property. The canvas takes care of making sure it has a valid device context, and releases the context when you're not using it. Similarly, the canvas has its own properties representing the current pen, brush, and font.

The canvas manages all those resources for you, so you need not concern yourself with creating, selecting, and releasing things such as pen handles. You just tell the canvas what kind of pen it should use, and it takes care of the rest.

One of the benefits of letting Delphi manage graphic resources is that it can cache resources for later use, which can greatly speed up repetitive operations. For example, if you have a program that repeatedly creates, uses, and disposes of a particular kind of pen tool, you need to repeat those steps each time you use it. Because Delphi caches graphic resources, chances are good that a tool you use repeatedly is still in the cache, so instead of having to recreate a tool, Delphi reuses an existing one.

Example

As an example of how much simpler Delphi's graphics code can be, here are two samples of code. The first uses standard GDI functions to draw a yellow ellipse outlined in blue on a window in an application written with ObjectWindows. The second uses a canvas to draw the same ellipse in an application written with Delphi.

procedure TMyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);

var

  PenHandle, OldPenHandle: HPEN;

  BrushHandle, OldBrushHandle: HBRUSH;

begin

  PenHandle := CreatePen(PS_SOLID, 1, RGB(0, 0, 255));       { create blue pen }

  OldPenHandle := SelectObject(PaintDC, PenHandle);        { tell DC to use blue pen }

  BrushHandle := CreateSolidBrush(RGB(255, 255, 0));        { create a yellow brush }

  OldBrushHandle := SelectObject(PaintDC, BrushHandle);         { tell DC to use yellow brush }

 

  Ellipse(HDC, 10, 10, 50, 50);    { draw the ellipse }

  SelectObject(OldBrushHandle);  { restore original brush }

  DeleteObject(BrushHandle);       { delete yellow brush }

  SelectObject(OldPenHandle);      { restore original pen }

  DeleteObject(PenHandle);  { destroy blue pen }

end;

 

procedure TForm1.FormPaint(Sender: TObject);

begin

  with Canvas do

  begin

    Pen.Color := clBlue;  { make the pen blue }

    Brush.Color := clYellow;   { make the brush yellow }

 

    Ellipse(10, 10, 50, 50); { draw the ellipse }

  end;

end;

 

Using the canvas

The canvas object encapsulates Windows graphics at several levels, ranging from high-level functions for drawing individual lines, shapes, and text to intermediate-level properties for manipulating the drawing capabilities of the canvas to low-level access to the Windows GDI.

The following table summarizes the capabilities of the canvas.

Level             Operation                              Tools

High                     Drawing lines and shapes             Methods such as MoveTo, LineTo, Rectangle, and Ellipse

                             Displaying and measuring text   TextOut, TextHeight, TextWidth, and TextRect methods

                             Filling areas                                      FillRect and FloodFill methods

Intermediate    Customizing text and graphics   Pen, Brush, and Font properties

                             Manipulating pixels                         Pixels property

                             Copying and merging images       Draw, StretchDraw, BrushCopy, and CopyRect methods; CopyMode property

Low                     Calling Windows GDI functions   Handle property

 

Working with pictures

Most of the graphics work you do in Delphi is limited to drawing directly on the canvases of components and forms. Delphi also provides for handling standalone graphic images, however, such as bitmaps, metafiles, and icons, including automatic management of palettes.

There are three important aspects to working with pictures in Delphi:

Picture or graphic or canvas?

A canvas, which represents a bitmapped drawing surface on a form, graphic control, printer, or bitmap. A canvas is always a property of something else, never a standalone object.

A graphic, which represents a graphic image of the sort usually found in a file or resource, such as a bitmap, icon, or metafile. Delphi defines object types TBitmap, TIcon, and TMetafile, all descended from a generic TGraphic. You can also define your own graphic objects. By defining a minimal standard interface for all graphics, TGraphic provides a simple mechanism for applications to use different kinds of graphics easily.

A picture, which is a container for a graphic, meaning it could contain any of the graphic object types. That is, an item of type TPicture can contain a bitmap, an icon, a metafile, or a user-defined graphic type, and an application can access them all in the same way through the picture object. For example, the image control has a property called Picture, of type TPicture, enabling the control to display images from many kinds of graphics.

Keep in mind that a picture object always has a graphic, and a graphic might have a canvas (the only standard graphic that has a canvas is TBitmap). Normally, when dealing with a picture, you work only with the parts of the graphic object exposed through TPicture. If you need access to the specifics of the graphic object itself, you can refer to the picture's Graphic property.

Graphics in files

All pictures and graphics in Delphi can load their images from files and store them back again (or into different files). You can load or store the image of a picture at any time.

To load an image into a picture from a file, call the picture's LoadFromFile method.

To save an image from a picture into a file, call the picture's SaveToFile method.

LoadFromFile and SaveToFile each take the name of a file as the only parameter. LoadFromFile uses the extension of the file name to determine what kind of graphic object it will create and load. SaveToFile saves whatever type of file is appropriate for the type of graphic object being saved.

Example

To load a bitmap into an image control's picture pass the name of a bitmap file to the picture's LoadFromFile method:

procedure TForm1.LoadBitmapClick(Sender: TObject);

begin

  Image1.Picture.LoadFromFile('RANDOM.BMP');

end;

The picture recognized .BMP as the standard extension for bitmap files, so it creates its graphic as a TBitmap, then calls that graphic's LoadFromFile method. Since the graphic is a bitmap, it loads the image from the file as a bitmap.

Handling palettes

When running on a palette-based device, Delphi controls automatically support palette realization. That is, if you have a control that has a palette, you can use two methods inherited from TControl to control how Windows accommodates that palette.

Palette support for controls has these two aspects:

 

Specifying a palette for a control

If you have a palette you want to apply to a control, you can tell your application to use that palette.

To specify a palette for a control, override the control's GetPalette method to return the handle of the palette.

Specifying the palette for a control does two things for your application:

Example

The clearest example of a control using a palette is the image control, TImage. The image control gets its palette (if any) from the picture it contains. TImage overrides GetPalette to return the palette of its picture:

type

  TImage = class(TGraphicControl)

  protected

    function GetPalette: HPALETTE; override;     { override the method }

  ...

  end;

...

function TImage.GetPalette: HPALETTE;

begin

  Result := 0;       { default result is no palette }

  if FPicture.Graphic is TBitmap then       { only bitmaps have palettes }

    Result := TBitmap(FPicture.Graphic).Palette; { use it if available }

end;

1  It tells the application that your control's palette needs to be realized.

2  It designates the palette to use for realization.

Responding to palette changes

If your control specifies a palette by overriding GetPalette, Delphi automatically takes care of responding to palette messages from Windows. The method that handles the palette messages is PaletteChanged. For normal operation, you should never need to alter the default behavior of PaletteChanged.

The primary role of PaletteChanged is to determine whether to realize the control's palette in the foreground or the background. Windows handles this realization of palettes by making the topmost window have a foreground palette, with other windows resolved in background palettes. Delphi goes one step farther, in that it also realizes palettes for controls within a window in tab order. The only time you might need to override this default behavior is if you want a control that is not first in tab order to have the foreground palette.

 

Most controls have no need for a palette. However, controls that contain graphic images (such as the image control) might need to interact with Windows and the screen device driver to ensure the proper appearance of the control. Windows refers to this process as realizing palettes.

Realizing palettes is the process of ensuring that the frontmost window uses its full palette, and that windows in the background use as much of their palettes as possible, then map any other colors to the closest available colors in the "real" palette. As windows move in front of one another, Windows continually realizes the palettes.

 

Note:   Delphi itself provides no specific support for creating or maintaining palettes, other than in bitmaps. However, if you have a palette handle, Delphi controls can manage it for you.

Offscreen bitmaps

 When drawing complex graphic images, a common technique in Windows programming is to create an offscreen bitmap, draw the image on the bitmap, and then copy the complete image from the bitmap to the final destination onscreen. Using an offscreen image reduces flicker caused by repeated drawing directly to the screen.

The bitmap object in Delphi to represent bitmapped images in resources and files can also work as an offscreen image.

There are two main aspects to working with offscreen bitmaps:

Creating and managing offscreen bitmaps

When creating complex graphic images, you should generally avoid drawing them directly on a canvas that appears onscreen. Instead of drawing on the canvas for a form or control, you can construct a bitmap object, draw on its canvas, and then copy its completed image to the onscreen canvas.

The most common use of an offscreen bitmap is in the Paint method of a graphic control. As with any temporary object, the bitmap should be protected with a try..finally block:

type

  TFancyControl = class(TGraphicControl)

  protected

    procedure Paint; override;         { override the Paint method }

  end;

procedure TFancyControl.Paint;

var

  Bitmap: TBitmap;  { temporary variable for the offscreen bitmap }

begin

  Bitmap := TBitmap.Create;  { construct the bitmap object }

  try

    { draw on the bitmap }

    { copy the result into the control's canvas }

  finally

    Bitmap.Free;      { destroy the bitmap object }

  end;

end;

Example

For an example of painting a complex image on an offscreen bitmap, see the source code for the Gauge control from the Samples page of the Component palette. The gauge draws its different shapes and text on an offscreen bitmap before copying them to the screen. Source code for the gauge is in the file GAUGES.PAS in the SOURCE/SAMPLES subdirectory.

Copying bitmapped images

Delphi provides four different ways to copy images from one canvas to another. Depending on the effect you want to create, you call different methods.

The following table summarizes the image-copying methods in canvas objects.

To create this effect                                 Call this method

Copy an entire graphic                                Draw

Copy and resize a graphic                          StretchDraw

Copy part of a canvas                                CopyRect

Copy a bitmap with raster operations  BrushCopy

Responding to changes

All graphic objects, including canvases and their owned objects (pens, brushes, and fonts) have events built into them for responding to changes in the object. By using these events, you can make your components (or the applications that use them) respond to changes by redrawing their images.

Responding to changes in graphic objects is particularly important if you publish those objects as part of the design-time interface of your components. The only way to ensure that the design-time appearance of the component matches the properties set in the Object Inspector is to respond to changes in the objects.

To respond to changes in a graphic object, assign a method to the object's OnChange event.

Example

The shape component publishes properties representing the pen and brush it uses to draw its shape. The component's constructor assigns a method to the OnChange event of each, causing the component to refresh its image if either the pen or brush changes:

type

  TShape = class(TGraphicControl)

  public

    procedure StyleChanged(Sender: TObject);

  end;

...

implementation

...

constructor TShape.Create(AOwner: TComponent);

begin

  inherited Create(AOwner);    { always call the inherited constructor! }

  Width := 65;

  Height := 65;

  FPen := TPen.Create;   { construct the pen }

  FPen.OnChange := StyleChanged;         { assign method to OnChange event }

 

  FBrush := TBrush.Create;   { construct the brush }

  FBrush.OnChange := StyleChanged;     { assign method to OnChange event }

end;

procedure TShape.StyleChanged(Sender: TObject);

begin

  Invalidate(True);    { erase and repaint the component }

end;


Components

The following are the components documented in the Delphi Component Writer's Reference. Many of them are also covered in the regular VCL reference. In those cases, the entries for component writers show only the protected parts of the components. For components that do not appear in the documentation for component users, all parts of the component are shown.

TApplication               TCustomMaskEdit             TMenu

TBevel                        TCustomMemo                   TMenuItem

TButton                      TCustomOutline                 TNotebook

TButtonControl         TCustomPanel                   TPaintBox

TCommonDialog         TCustomRadioGroup         TRadioButton

TComponent               TFindDialog                        TReplaceDialog

TControl                     TFontDialog                       TScrollBar

TCustomCheckBox    TForm                                 TScrollBox

TCustomComboBox   TGraphicControl                TScrollingWinControl

TCustomControl        THeader                             TShape

TCustomEdit             THintWindow                      TTabSet

TCustomGrid              TImage                                TTimer

TCustomGroupBox    TInplaceEdit                      TWinControl

TCustomLabel           TMediaPlayer                    

TCustomListBox       Tmemo

 

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值