How can I create a simple property editor ?
This is an introductory level article about creating a simple property editor that I
hope will get you started. I'll provide enough information about the DSGNINTF.PAS
(Design interface that holds the TPropertyEditor class) file so you can finish the article
with a sense of that you are able to create a property editor.
TPropertyEditor
The following is a cut and paste of the TPropertyEditor class declaration found in
DSGNINTF.PAS:
TPropertyEditor = class
private
FDesigner: TFormDesigner;
FPropList: PInstPropList;
FPropCount: Integer;
constructor Create(ADesigner: TFormDesigner; APropCount: Integer);
function GetPrivateDirectory: string;
procedure SetPropEntry(Index: Integer; AInstance: TComponent;
APropInfo: PPropInfo);
protected
function GetPropInfo: PPropInfo;
function GetFloatValue: Extended;
function GetFloatValueAt(Index: Integer): Extended;
function GetMethodValue: TMethod;
function GetMethodValueAt(Index: Integer): TMethod;
function GetOrdValue: Longint;
function GetOrdValueAt(Index: Integer): Longint;
function GetStrValue: string;
function GetStrValueAt(Index: Integer): string;
function GetVarValue: Variant;
function GetVarValueAt(Index: Integer): Variant;
procedure Modified;
procedure SetFloatValue(Value: Extended);
procedure SetMethodValue(const Value: TMethod);
procedure SetOrdValue(Value: Longint);
procedure SetStrValue(const Value: string);
procedure SetVarValue(const Value: Variant);
public
destructor Destroy; override;
procedure Activate; virtual;
function AllEqual: Boolean; virtual;
procedure Edit; virtual;
function GetAttributes: TPropertyAttributes; virtual;
function GetComponent(Index: Integer): TComponent;
function GetEditLimit: Integer; virtual;
function GetName: string; virtual;
procedure GetProperties(Proc: TGetPropEditProc); virtual;
function GetPropType: PTypeInfo;
function GetValue: string; virtual;
procedure GetValues(Proc: TGetStrProc); virtual;
procedure Initialize; virtual;
procedure Revert;
procedure SetValue(const Value: string); virtual;
function ValueAvailable: Boolean;
property Designer: TFormDesigner read FDesigner;
property PrivateDirectory: string read GetPrivateDirectory;
property PropCount: Integer read FPropCount;
property Value: string read GetValue write SetValue;
end;
Whew! that's a lot of stuff, isn't it? Add to the fact that the Tools API is poorly
documented, and you've got a lot confusion to deal with. There's a little relief in the
Delphi 2.0 help file, but the way it's organized can leave you with the sinking feeling
that you're in way over your head. After you've done a few property editors, it's really
not that hard.
The Trick to Writing Property Editors
One of the biggest problems with technical documentation is that it's technical. Not
much conceptual material is ever covered in tech specs or tech manuals. This leaves it up
to the programmer to extrapolate the underlying concepts. I'm of the opinion that if
something has the possibility of being a widely used feature, you should cover not only
the technical specifications, but the conceptual points as well. Gaining conceptual
understanding is the real trick to creating property.
The trick to writing property editors is understanding the virtual methods and
what they do. Writing your own custom property editors is all about overriding the
proper methods of the TPropertyEditor class to get the functionality out of property
editor that you require. Granted, there are a lot of very complex property editors out
there. But whether simple or complex, they're all built in a similar fashion: they
override default functionality of TPropertyEditor.
Once you let this concept sink in, and as you gain more experience in building
components, writing a property editor merely becomes the task of overriding the
appropriate methods to get your job done.
Furthermore, the DSGNINFT.PAS is thoroughly commented. When constructing
components that will have property editors, make sure that this file is open in the editor
so you can refer to the documentation covering the virtual methods you will be overriding.
As an aside, if you do BDE programming, having the BDE.INT (Delphi 2.0) or DBIPROCS.INT,
DBITYPES.INT, DBIERRS.INT (Delphi 1.0) is essential to successful BDE programming
The Value List: the Simplest Type of Property Editor
Properties that display value lists are common to components. In fact, you see them all
the time. For instance, a value list property that everyone has used is the Align
property.
Value lists are simple enumerated types, which are merely a collection of sequentially
ordered elements in a list. The first item has an ordinal value of 0, the second 1, and so
forth. Enumerated types are useful in communicating with the user using a set of named
choices rather than ordinal or numeric choices. For instance, the Align element alBottom
is much easier to understand than '0,' which is its ordinal value in the list. In this
case, the ordinal value has no clear conceptual context.
To create a property editor that presents a value list to a user in the object
inspector is very simple and requires only a few steps. Here is a brief synopsis of what
you have to do before we go into detail:
- First, define and declare your enumerated type under a new type section.
- Under the enumerated type declaration, declare your class, including the functions you
will be overriding in your code.
- Write your code in the implementation section of the unit.
Sounds pretty simple, right? It is. So let's go and create one now, then we'll discuss
it in detail below.
...other code
interface
type
TEnumMonth = (emJan, emFeb, emMar,
emApr, emMay, emJun,
emJul, emAug, emSep,
emOct, emNov, emDec);
TEnumMonths = class(TEnumProperty)
public
function GetAttributes: TPropertyAttributes; override;
function AllEqual : Boolean; override;
end;
implementation
...other code
function TEnumMonths.AllEqual: Boolean;
begin
Result := True;
end;
function TEnumMonths.GetAttributes: TPropertyAttributes;
begin
Result := [paMultiSelect, paValueList];
end;
procedure Register;
begin
RegisterPropertyEditor(TypeInfo(TEnumMonth), TPSIBaseExt, 'RangeBegin', TEnumMonths);
RegisterPropertyEditor(TypeInfo(TEnumMonth), TPSIBaseExt, 'RangeEnd', TEnumMonths);
end;
The property editor listed above was created to serve a singular purpose: Allow the
user to select a specific month from a list of months, rather than typing in a month value
code himself (which is more work than the user needs and is also prone to spelling
errors).
This component modernizes a cumbersome style of interaction in existing applications.
In these applications users were required to enter month ranges as a six-digit string
beginning with current two-digit year plus the four digit month/day combination (eg., YYMMDD).
Past experience said that runtime errors or empty result sets from queries that used the
range values were usually the result of mistyping. So the property editor was created to
let the user pick a month for both the starting month and ending month of the range of
values they wanted to extract. This explains why in the code above I registered the
property editor for both the RangeBegin and RangeEnd properties.
Elsewhere in the component, I have created an array type of type String and created two
arrays representing the starting month and ending month code values, respectively.
Here's the array type declaration:
type
TMonthRng = Array[0..11] of String;
....
Here are the declarations and initializations of the arrays themselves:
var
stmonArr,
enmonArr : TMonthRng;
begin
stmonArr[0] := '0101';
stmonArr[1] := '0201';
stmonArr[2] := '0301';
stmonArr[3] := '0401';
stmonArr[4] := '0501';
stmonArr[5] := '0601';
stmonArr[6] := '0701';
stmonArr[7] := '0801';
stmonArr[8] := '0901';
stmonArr[9] := '1001';
stmonArr[10] := '1101';
stmonArr[11] := '1201';
enmonArr[0] := '0131';
enmonArr[1] := '0229';
enmonArr[2] := '0331';
enmonArr[3] := '0430';
enmonArr[4] := '0531';
enmonArr[5] := '0630';
enmonArr[6] := '0731';
enmonArr[7] := '0831';
enmonArr[8] := '0930';
enmonArr[9] := '1031';
enmonArr[10] := '1130';
enmonArr[11] := '1231';
...
By doing things in this manner, one can easily get the appropriate value needed by
passing the ordinal value of the appropriate enumerated type as an index of an element in
the array. For example, let's say the user chose emApr as his/her starting month. The
ordinal value of emApr is 3. Referencing that value in the stmonArr array would produce
the string '0401.' What I've essentially done here is eliminate the need for the user to
do anything more than choose an appropriate month to start with. The proper code is
handled by the program. Here's some sample code that demonstrates how it's done:
procedure ReturnMonthCode(Index : Integer; StartMonth : Boolean) : String;
var
stmonArr,
enmonArr : TMonthRng;
begin
stmonArr[0] := '0101';
stmonArr[1] := '0201';
stmonArr[2] := '0301';
stmonArr[3] := '0401';
stmonArr[4] := '0501';
stmonArr[5] := '0601';
stmonArr[6] := '0701';
stmonArr[7] := '0801';
stmonArr[8] := '0901';
stmonArr[9] := '1001';
stmonArr[10] := '1101';
stmonArr[11] := '1201';
enmonArr[0] := '0131';
enmonArr[1] := '0229';
enmonArr[2] := '0331';
enmonArr[3] := '0430';
enmonArr[4] := '0531';
enmonArr[5] := '0630';
enmonArr[6] := '0731';
enmonArr[7] := '0831';
enmonArr[8] := '0930';
enmonArr[9] := '1031';
enmonArr[10] := '1130';
enmonArr[11] := '1231';
if StartMonth then
Result := stMonArr[Index]
else
Result := enMonArr[Index];
end;
To actually use ReturnMonthCode all we do is the following:
var
S : String;
begin
S := ReturnMonthCode(Ord(RangeBegin), True);
Remember, RangeBegin is a property of type TEnumArray. Therefore, to access its ordinal
value, all we need do is apply the Ord function to it.
Based on the information above, you should be able to create at the very least a simple
property editor like the example above. For more complex property editors, you will have
to override more of the methods; but remember, don't be daunted by the code. The trick
is overriding the default methods with your own.
Copyright © 1997 Brendan V. Delumpa
|