Author: Lou Adler
How can I create a simple property editor?
Answer:
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:
1
2 TPropertyEditor = class
3 private
4 FDesigner: TFormDesigner;
5 FPropList: PInstPropList;
6 FPropCount: Integer;
7 constructor Create(ADesigner: TFormDesigner; APropCount: Integer);
8 function GetPrivateDirectory: string;
9 procedure SetPropEntry(Index: Integer; AInstance: TComponent;
10 APropInfo: PPropInfo);
11 protected
12 function GetPropInfo: PPropInfo;
13 function GetFloatValue: Extended;
14 function GetFloatValueAt(Index: Integer): Extended;
15 function GetMethodValue: TMethod;
16 function GetMethodValueAt(Index: Integer): TMethod;
17 function GetOrdValue: Longint;
18 function GetOrdValueAt(Index: Integer): Longint;
19 function GetStrValue: string;
20 function GetStrValueAt(Index: Integer): string;
21 function GetVarValue: Variant;
22 function GetVarValueAt(Index: Integer): Variant;
23 procedure Modified;
24 procedure SetFloatValue(Value: Extended);
25 procedure SetMethodValue(const Value: TMethod);
26 procedure SetOrdValue(Value: Longint);
27 procedure SetStrValue(const Value: string);
28 procedure SetVarValue(const Value: Variant);
29 public
30 destructor Destroy; override;
31 procedure Activate; virtual;
32 function AllEqual: Boolean; virtual;
33 procedure Edit; virtual;
34 function GetAttributes: TPropertyAttributes; virtual;
35 function GetComponent(Index: Integer): TComponent;
36 function GetEditLimit: Integer; virtual;
37 function GetName: string; virtual;
38 procedure GetProperties(Proc: TGetPropEditProc); virtual;
39 function GetPropType: PTypeInfo;
40 function GetValue: string; virtual;
41 procedure GetValues(Proc: TGetStrProc); virtual;
42 procedure Initialize; virtual;
43 procedure Revert;
44 procedure SetValue(const Value: string); virtual;
45 function ValueAvailable: Boolean;
46 property Designer: TFormDesigner read FDesigner;
47 property PrivateDirectory: string read GetPrivateDirectory;
48 property PropCount: Integer read FPropCount;
49 property Value: string read GetValue write SetValue;
50 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
51 interface
52
53 type
54 TEnumMonth = (emJan, emFeb, emMar,
55 emApr, emMay, emJun,
56 emJul, emAug, emSep,
57 emOct, emNov, emDec);
58
59 TEnumMonths = class(TEnumProperty)
60 public
61 function GetAttributes: TPropertyAttributes; override;
62 function AllEqual: Boolean; override;
63 end;
64
65 implementation
66
67 ...other code
68
69 function TEnumMonths.AllEqual: Boolean;
70 begin
71 Result := True;
72 end;
73
74 function TEnumMonths.GetAttributes: TPropertyAttributes;
75 begin
76 Result := [paMultiSelect, paValueList];
77 end;
78
79 procedure register;
80 begin
81 RegisterPropertyEditor(TypeInfo(TEnumMonth), TPSIBaseExt, 'RangeBegin',
82 TEnumMonths);
83 RegisterPropertyEditor(TypeInfo(TEnumMonth), TPSIBaseExt, 'RangeEnd',
84 TEnumMonths);
85 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:
86 type
87 TMonthRng = array[0..11] of string;
88 ....
89
90 Here are the declarations and initializations of the arrays themselves:
91
92 var
93 stmonArr,
94 enmonArr: TMonthRng;
95 begin
96 stmonArr[0] := '0101';
97 stmonArr[1] := '0201';
98 stmonArr[2] := '0301';
99 stmonArr[3] := '0401';
100 stmonArr[4] := '0501';
101 stmonArr[5] := '0601';
102 stmonArr[6] := '0701';
103 stmonArr[7] := '0801';
104 stmonArr[8] := '0901';
105 stmonArr[9] := '1001';
106 stmonArr[10] := '1101';
107 stmonArr[11] := '1201';
108 enmonArr[0] := '0131';
109 enmonArr[1] := '0229';
110 enmonArr[2] := '0331';
111 enmonArr[3] := '0430';
112 enmonArr[4] := '0531';
113 enmonArr[5] := '0630';
114 enmonArr[6] := '0731';
115 enmonArr[7] := '0831';
116 enmonArr[8] := '0930';
117 enmonArr[9] := '1031';
118 enmonArr[10] := '1130';
119 enmonArr[11] := '1231';
120
121 ...
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:
122
123 procedure ReturnMonthCode(Index: Integer; StartMonth: Boolean): string;
124 var
125 stmonArr,
126 enmonArr: TMonthRng;
127 begin
128 stmonArr[0] := '0101';
129 stmonArr[1] := '0201';
130 stmonArr[2] := '0301';
131 stmonArr[3] := '0401';
132 stmonArr[4] := '0501';
133 stmonArr[5] := '0601';
134 stmonArr[6] := '0701';
135 stmonArr[7] := '0801';
136 stmonArr[8] := '0901';
137 stmonArr[9] := '1001';
138 stmonArr[10] := '1101';
139 stmonArr[11] := '1201';
140
141 enmonArr[0] := '0131';
142 enmonArr[1] := '0229';
143 enmonArr[2] := '0331';
144 enmonArr[3] := '0430';
145 enmonArr[4] := '0531';
146 enmonArr[5] := '0630';
147 enmonArr[6] := '0731';
148 enmonArr[7] := '0831';
149 enmonArr[8] := '0930';
150 enmonArr[9] := '1031';
151 enmonArr[10] := '1130';
152 enmonArr[11] := '1231';
153
154 if StartMonth then
155 Result := stMonArr[Index]
156 else
157 Result := enMonArr[Index];
158 end;
159
160 //To actually use ReturnMonthCode all we do is the following:
161
162 var
163 S: string;
164 begin
165
166 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.
|