Creating Custom Property Editors

   

One of the best ways to improve a component's design-time interface is to ensure that property editors are easy to use and intuitive. This section looks at the main principles involved in creating your own property editors. All custom property editors descend ultimately from TPropertyEditor , which provides the basic functionality required for the editor to function within the IDE. Listing 5.1 shows the the class definition for TPropertyEditor (in C++ Builder 5 and earlier, this is from $(BCB)\Include\Vcl\VCLEditors.hpp , where $(BCB) is the C++Builder installation directory. In C++Builder 6, this has been moved to $(BCB)\Include\VCL\VCLEditors.hpp or $(BCB)\Include\VCL\CLXEditors.hpp depending on whether you are constructing editors for Windows alone or for CLX compatibility).

Listing 5.1 TPropertyEditor Class Definition
[View full width]
 class DELPHICLASS TPropertyEditor;  class PASCALIMPLEMENTATION TPropertyEditor : public Designintf::TBasePropertyEditor  {       typedef Designintf::TBasePropertyEditor inherited;  private:       Designintf::_di_IDesigner FDesigner;       TInstProp *FPropList;       int FPropCount;       AnsiString __fastcall GetPrivateDirectory();  protected:       virtual void __fastcall SetPropEntry(int Index, Classes::TPersistent* AInstance, graphics/ccc.gif Typinfo::PPropInfo APropInfo);       Extended __fastcall GetFloatValue(void);       Extended __fastcall GetFloatValueAt(int Index);       __int64 __fastcall GetInt64Value(void);       __int64 __fastcall GetInt64ValueAt(int Index);       System::TMethod __fastcall GetMethodValue();       System::TMethod __fastcall GetMethodValueAt(int Index);       int __fastcall GetOrdValue(void);       int __fastcall GetOrdValueAt(int Index);       AnsiString __fastcall GetStrValue();       AnsiString __fastcall GetStrValueAt(int Index);       Variant __fastcall GetVarValue();  Variant __fastcall GetVarValueAt(int Index);       System::_di_IInterface __fastcall GetIntfValue();       System::_di_IInterface __fastcall GetIntfValueAt(int Index);       void __fastcall Modified(void);       void __fastcall SetFloatValue(Extended Value);       void __fastcall SetMethodValue(const System::TMethod &Value);       void __fastcall SetInt64Value(__int64 Value);       void __fastcall SetOrdValue(int Value);       void __fastcall SetStrValue(const AnsiString Value);       void __fastcall SetVarValue(const Variant &Value);       void __fastcall SetIntfValue(const System::_di_IInterface Value);       bool __fastcall GetEditValue(/* out */ AnsiString &Value);       bool __fastcall HasInstance(Classes::TPersistent* Instance);  public:       __fastcall virtual TPropertyEditor(const Designintf::_di_IDesigner ADesigner, int graphics/ccc.gif APropCount);       __fastcall virtual ~TPropertyEditor(void);       virtual void __fastcall Activate(void);       virtual bool __fastcall AllEqual(void);       virtual bool __fastcall AutoFill(void);       virtual void __fastcall Edit(void);       virtual Designintf::TPropertyAttributes __fastcall GetAttributes(void);       Classes::TPersistent* __fastcall GetComponent(int Index);       virtual int __fastcall GetEditLimit(void);       virtual AnsiString __fastcall GetName();       virtual void __fastcall GetProperties(Designintf::TGetPropProc Proc);       virtual Typinfo::PPropInfo __fastcall GetPropInfo(void);       Typinfo::PTypeInfo __fastcall GetPropType(void);       virtual AnsiString __fastcall GetValue();       AnsiString __fastcall GetVisualValue();       virtual void __fastcall GetValues(Classes::TGetStrProc Proc);       virtual void __fastcall Initialize(void);       void __fastcall Revert(void);       virtual void __fastcall SetValue(const AnsiString Value);       bool __fastcall ValueAvailable(void);       __property Designintf::_di_IDesigner Designer = {read=FDesigner};       __property AnsiString PrivateDirectory = {read=GetPrivateDirectory};       __property int PropCount = {read=FPropCount, nodefault};       __property AnsiString Value = {read=GetValue, write=SetValue};  private:       void *__IProperty;       /* Designintf::IProperty */  public:       operator IProperty*(void) { return (IProperty*)&__IProperty; }  };  

To customize the editor's behavior, one or more TPropertyEditor virtual (or DYNAMIC ) functions must be overridden. You can save a lot of coding by deriving your custom property editor from the most appropriate property editor class. The hierarchy of TPropertyEditor descendants is shown in Figure 5.1. Descendants in shaded boxes are those that override the custom rendering functionality of TPropertyEditor .

Figure 5.1. The TPropertyEditor inheritance hierarchy.

graphics/05fig01.gif

The hierarchy shown in Figure 5.1 is useful when deciding which property editor to inherit from. The purpose of each property editor is fairly self-explanatory, with the exception of one or two of the more specialized. For your convenience, brief descriptions of the more commonly encountered property editors are given in Table 5.2.

Table 5.2. Common Property Editor Classes and Their Uses

Property Editor Class

Use

TCaptionProperty

The editor for all Caption and Text named AnsiString properties. The Caption property of TForm and the Text property of TEdit are examples. The difference between this property editor and the TStringProperty from which it derives is that the component being edited is continually updated as the property is edited. With TStringProperty the updating occurs after the edit has finished.

TCharProperty

The default editor for all char properties and subtypes of char . Displays either the character of the property's value or the value itself preceded by the # character. The PasswordChar ( char ) property of TMaskEdit is an example.

TClassProperty

The default editor for TPersistent -derived class properties. Published properties of the class are displayed as subproperties when the + image before the property name is clicked. The Constraints ( TSizeConstraints* ) property of TForm is an example.

TColorProperty

The default editor for TColor type properties. Displays the color as a cl XXX value if one exists, otherwise displays the value as hexadecimal (in BGR format; 0x00 BBGGRR ). The value can be entered as either a cl XXX value or as a number. Also allows the cl XXX value to be picked from a list. When the property is double-clicked, the Color dialog is displayed. The Color ( TColor ) property of TForm is an example.

TComponentProperty

Thedefault editor for pointers to TComponent -derived objects. The editor displays a drop-down list of type-compatible objects that appear in the same form as the component being edited. The Images ( TCustomImageList* ) property of TToolBar is an example.

TCursorProperty

For TCursor properties. Allows a cursor to be selected from a list that gives each cursor's name and its corresponding image. The Cursor ( TCursor ) property of TForm is an example.

TEnumProperty

Thedefault editor for all enum -based properties. A drop-down list displays the possible values the property can take. The Align ( TAlign ) and BorderStyle ( TFormBorderStyle ) properties of TForm are examples.

TFloatProperty

The default editor for all floating-point “based properties, namely double , long double , and float . The PrintLeftMargin ( double ) and PrintRightMargin ( double ) properties of TF1Book are examples.

TFontProperty

For TFont properties. The editor allows the font settings to be edited either through the Font dialog (by clicking the ellipses button) or by editing an expandable list of subproperties. The Font ( TFont ) property of TForm is an example.

TIntegerProperty

The default editor for all int properties. The Height ( int ) and Width ( int ) properties of TForm are examples.

TMethodProperty

The default editor for pointer-to-method (member function) properties; that is, events. The editor displays a drop-down list of event handlers for the event type matching that of the property. The OnClick and OnClose events of TForm are examples.

TOrdinalProperty

All ordinal-based (that is integral) property editors ultimately descend from this class, such as TIntegerProperty , TCharProperty , TenumProperty , and TSetProperty .

TPropertyEditor

The class from which all property editors are descended.

TSetElementProperty

This editor is used to edit the individual elements of a Set . The property can be set to true to indicate that the element is contained in the Set and false to indicate that it is not.

TSetProperty

The default editor for all Set properties. Each element of the Set is displayed as a subproperty of the Set property allowing each element to be removed from or added to the Set as desired. The Anchors ( TAnchors ) and BorderIcons ( TBorderIcons ) properties of TForm are examples.

TStringProperty

The default editor for AnsiString properties. The Hint and Name properties of TForm are examples.

Choosing the right property editor to inherit from is linked inextricably with the requirements specification of the property editor. In fact, the hardest part of creating a custom property editor is deciding exactly what behavior is required. This is an issue that will come up later in this section.

The stages of developing a new property editor are summarized in the following list:

  1. Decide exactly how you want the editor to behave. Property editors often are developed to offer a bounded choice that ensures proper component operation and an intuitive interface. The nature of bounding, such as to restrict the user to a choice of some discrete predefined values, must be decided.

  2. Decide whether a custom property editor is even required. By slightly changing how a property is used, it might be that no custom property editor is necessary. To this end, it is important to know which property editors are registered for which property types; Table 5.2 can be used as a guide. Because this section is about creating custom property editors, that alternative will not be explored further. Needless to say, you cannot know too much about the existing property editors and how they work. A good source of information is the $(BCB)\Source\ToolsApi\VCLEditors.pas file.

  3. Choose carefully the property editor from which your custom property editor descends. A careful choice can save a lot of unnecessary coding.

  4. Decide which property attributes are applicable to your property editor.

  5. Determine which functions of the parent property editor need to be overridden and which do not.

  6. Finally, write the necessary code and try it out.

After it has been decided that a custom property editor is required and the parent property editor class has been chosen , the next step is to decide which property attributes are suitable. Every property editor has a method called GetAttributes() that returns a TPropertyAttributes Set . This tells the Object Inspector how the property is to be used. For example, if the property displays a drop-down list of values, you must ensure that paValueList is contained by the TPropertyAttributes Set returned by the property editor's GetAttributes() method. Unless the property attributes of the parent property editor class exactly match those required in the custom property editor, the GetAttributes() method must be overridden. Table 5.3 shows the different values that can be contained by the TPropertyAttributes Set . Methods that might require overridding as a result of the property editor having a particular attribute are also shown.

Table 5.3. TPropertyAttributes Set Values

Value

Purpose

paAutoUpdate

Properties whose editors have this attribute are updated automatically because they are changed in the Object Inspector, such as the Caption property of TLabel . Normally, a property will not be updated until the Return key is pressed or focus leaves the property.

SetValue() is called to convert the AnsiString representation to the proper format and ensure the value is valid. Overriding SetValue() is probably necessary.

Override: SetValue(const AnsiString Value)

paDialog

Properties with this attribute display an ellipsis button ( ... ) on the right side of the property value region. When clicked, this displays a form to allow the property to be edited. When the ellipses button is pressed, the Edit() method of the property editor is invoked. This must, therefore, be overridden for properties with this attribute. Override: Edit()

paFullWidthName

Properties with this attribute do not display a value region in the Object Inspector. Rather, the property name extends to the full width of the Object Inspector.

paMultiSelect

Properties whose editors have this attribute can be edited when more than one component is selected on a form. For example, the property editor for the Caption property of TLabel and TButton has this attribute. When several TLabel and TButton components are placed on a form and selected, the Caption properties can be edited simultaneously . The Object Inspector displays all properties whose editors have the paMultiSelect attribute and whose property names and types are exactly the same.

paReadOnly

Properties whose editors have this attribute cannot be edited in the Object Inspector.

paRevertable

Properties whose editors have this attribute enable the Revert to Inherited menu item in the Object Inspector's context menu, allowing the property editor to revert the current property value to some default value.

paSortList

Properties with this attribute have their value lists sorted by the Object Inspector.

paSubProperties

Properties with this attribute tell the Object Inspector that the property editor has subproperties that can be edited. A + symbol is placed in front of the property name. The TFont property editor is an example of this. To tell the Object Inspector which subproperties to display, GetProperties() must be overridden.

Override: GetProperties(TGetPropEditProc Proc)

paValueList

Properties whose editors have this attribute display a drop-down list of possible values that the property can take. A value can still be entered manually in the editable property value region. For example, TColor properties behave this way. To provide a list of values for the Object Inspector to display, you must override the GetValues() method. Override: GetValues(Classes::TGetStrProc Proc)

After the attributes of the property editor have been decided, it is easy to see which methods of the parent property editor must be overridden. Other methods can also require overriding; this will depend on the specifications of the property editor. Table 5.4 lists the virtual and DYNAMIC methods of TPropertyEditor . The methods are grouped and ordered according to their use; they are not listed alphabetically .

Table 5.4. The virtual and DYNAMIC Methods of TPropertyEditor

Method

Declaration and Purpose

GetAttributes()

virtual TPropertyAttributes __fastcall GetAttributes(void); Returns a TPropertyAttributes Set . Invoked to set the property editor attributes.

GetValue()

virtual AnsiString __fastcall GetValue(); Returns an AnsiString that represents the property's value. By default (that is, in TPropertyEditor ) it returns (unknown) . Therefore, if you derive directly from TPropertyEditor , you must override this method to return the correct value.

SetValue()

virtual void __fastcall SetValue(const AnsiString Value); Called to set the value of a property. SetValue() must convert the AnsiString representation of the property's value to a suitable format. If an invalid value is entered, SetValue() should throw an exception that describes the error. Note that SetValue() takes a const AnsiString as its parameter and returns void . An exception, therefore, is the only appropriate method of dealing with invalid values.

Edit()

virtual void __fastcall Edit(void);

Invoked when the ellipses button is pressed or the property is double-clicked ( GetAttributes() should return paDialog ). Normally used to display a form to allow more intuitive editing of the property value. Edit() can call GetValue() and SetValue() , or it can read and write the property value directly. If this is the case, input validation should be carried out. If an invalid value is entered, an exception describing the error should be thrown.

GetValues()

virtual void __fastcall

GetValues(Classes::TGetStrProc Proc);

Only called when paValueList is returned by GetAttributes() . The single parameter Proc is of type TGetStrProc , a __closure (pointer to an instance member function), declared in

$(BCB)\Include\Vcl\Classes.hpp as:

typedef void __fastcall (__closure *TGetStrProc)(const

AnsiString S) .

The Proc parameter is in fact the address of a method with a const AnsiString called S as its single parameter, which adds the AnsiString passed to the property editor's drop-down list. Call Proc(const AnsiString S) once for every value that should be displayed in the property value's drop-down list, for example:

Proc( value1 ); // value1 is an AnsiString ,

Proc( value2 ); // value2 is an AnsiString ,

and so on.

Activate()

virtual void __fastcall Activate(void);

Invoked when the property is selected in the Object Inspector. Enables the property editor attributes to be determined only when the property becomes selected (with the exception of paSubProperties and paMultiSelect ).

AllEqual()

virtual bool __fastcall AllEqual(void); Returns a bool value. Called only when paMultiSelect is one of the property editor's attributes (when it is returned by GetAttributes() ). It determines if all properties of the same name and type for which that editor is registered are equal when more than one is selected at once (it returns true ). If this is the case (they are equal), GetValue() is called to display the value; otherwise the value region is blanked.

AutoFill()

virtual bool __fastcall AutoFill(void);

Returns a bool value. Called only when paValueList is returned by GetAttributes() , it determines whether or not (returns true or false ) the values returned by GetValues() can be selected incrementally in the Object Inspector. By default it returns true .

GetEditLimit()

virtual int __fastcall GetEditLimit(void); Returns an int representing the maximum number of input characters allowed in the Object Inspector for this property. Overriding this method allows this number to be changed. The default value for the Object Inspector is 255 .

GetName()

virtual AnsiString __fastcall GetName(); Returns an AnsiString that is used by the Object Inspector to display the property name. This should be overridden only when the name determined from the property's type information is not the name that you want to appear in the Object Inspector.

GetProperties()

virtual void __fastcall

GetProperties(TGetPropEditProc Proc);

If it is required that subproperties be displayed, you must override this method. The single parameter Proc is of type TGetPropEditProc , a __closure declared in $(BCB)\Include\Vcl\VCLEditors.hpp as

typedef void __fastcall (__closure

*TGetPropEditProc)(TPropertyEditor* Prop);

Proc is, therefore, the address of a method with a pointer to a TPropertyEditor -derived editor called Prop as its single parameter. Call Proc(TPropertyEditor* Prop) once for each subproperty, passing a pointer to a TPropertyEditor -derived editor as an argument. For example, TSetProperty overrides this method and passes a TSetElementProperty pointer for each element in its Set . TClassProperty also overrides the GetProperties() method, displaying a subproperty for each of the class's published properties.

Initialize()

virtual void __fastcall Initialize(void);

This is invoked when the Object Inspector is going to use the property editor. Initialize() is called after the property editor has been constructed, but before it is used. When several components are selected at once, property editors are constructed , but are often discarded because they will not be used. This method allows the possibility of postponing certain operations until it is certain that they will be required.

The following are methods inherited from the interface IcustomPropertyEditor. If you want to implement these, you must add ICustomPropertyEditor to the inheritance list for your property editor ”it doesn't come automatically when you inherit from other property editors. Note that inheriting interfaces is the only exception to the rule that prevents multiple inheritance in VCL classes.

ListMeasureWidth()

virtual void __fastcall ListMeasureWidth(const AnsiString Value, Graphics::TCanvas* ACanvas, int& AWidth);

This is called during the width calculation phase of the property's drop-down list. If images are to be placed alongside text in the drop-down list, this method should be overridden to ensure the list is wide enough.

ListMeasureHeight()

virtual void __fastcall ListMeasureHeight(const AnsiString Value, Graphics::TCanvas* ACanvas, int& AHeight);

This is called during the height calculation phase of the property's drop-down list. If an image's height is greater than that of the property value text, this must be overridden to prevent clipping the image.

ListDrawValue()

virtual void __fastcall ListDrawValue(const AnsiString Value, Graphics::TCanvas* ACanvas, const TRect& ARect, bool ASelected);

This is called to render the current list item in the property's drop-down list. If an image is to be rendered, this method must be overridden. The default behavior of this method is to render the text representing the current list value.

PropDrawValue()

virtual void __fastcall PropDrawValue(Graphics::TCanvas* ACanvas, const TRect& ARect, bool ASelected);

This is called when the property value itself is to be rendered in the Object Inspector. If an image is to be rendered with the property value, this method must be overridden.

PropDrawName()

virtual void __fastcall PropDrawName(Graphics::TCanvas* ACanvas, const TRect &ARect, bool ASelected);

This is called when the property name is to be rendered in the Object Inspector. If an image is to be rendered with the property name, this method must be overridden. However, this is rarely needed.

Now, you should have a reasonable idea of the capabilities that can be implemented for a custom property editor. The next few sections look at some of the most important methods and present basic coding guidelines for their proper use. The last five methods ( ListMeasureWidth() , ListMeasureHeight() , ListDrawValue() , PropDrawValue() , and PropDrawName() ) are concerned with rendering images in the Object Inspector and are looked at in the section "Using Images in Property Editors," later in this chapter.

The methods that are most often overridden by custom property editors are GetAttributes() , GetValue() , SetValue() , Edit() , and GetValues() , the first five methods in Table 5.4. Listing 5.2 shows a class definition for a custom property editor derived from TPropertyEditor .

TIP

A key point to note in Listing 5.2 is the use of the typedef :

 typedef TPropertyEditor inherited; 

This allows inherited to be used as a namespace modifier in place of TPropertyEditor . This is commonly encountered in the VCL and makes it easy to call parent (in this case TPropertyEditor ) methods explicitly while retaining code maintainability. If the name of the parent class changes, only this one occurrence needs to be updated.

For example, you can write code such as this in the property editor's GetAttributes() method:

 return inherited::GetAttributes() << paValueList >> paMultiSelect 

This calls the property editor's base class GetAttributes() method, returning a TPropertyAttributes Set . paValueList is added to this Set , and paMultiSelect is removed from the Set . The final Set is returned.


Listing 5.2 Definition Code for a Custom Property Editor
 class TExamplePropertyEditor : public TPropertyEditor  {          typedef TPropertyEditor inherited;  public:     virtual TPropertyAttributes __fastcall GetAttributes(void);     virtual AnsiString __fastcall GetValue();     virtual void __fastcall SetValue(const AnsiString Value);     virtual void __fastcall Edit(void);     virtual void __fastcall GetValues(Classes::TGetStrProc Proc);  protected:     #pragma option push -w-inl     inline __fastcall virtual        TExamplePropertyEditor(const _DesignIntf::di_IDesigner  ADesigner,                              int APropCount)                              : TPropertyEditor(ADesigner, APropCount)     { }     #pragma option pop  public:     #pragma option push -w-inl     inline __fastcall virtual ~TCustomProperty(void) { }     #pragma option pop  };  

The GetAttributes() Method

The GetAttributes() method is very simple to implement. The only consideration is you should change just the attributes returned by the parent class that have a direct effect on your code. Remaining attributes should be unchanged so that you add only attributes that you definitely need and remove only attributes that you definitely don't want. Be sure to check the attributes of the parent class. You might not need to change them at all. For example, a property editor that derives directly from TPropertyEditor is required to display a drop-down list of values and should not be used when multiple components are selected. Suitable code for the GetAttributes() method is

 TPropertyAttributes __fastcall TExamplePropertyEditor::GetAttributes(void)  {     return TPropertyEditor::GetAttributes()        << paValueList >> paMultiSelect;  } 

Because TPropertyEditor::GetAttributes() returns paRevertable , the following is the same:

 TPropertyAttributes __fastcall TExamplePropertyEditor::GetAttributes(void)  {    return TPropertyAttributes() << paValueList << paRevertable >> paMultiSelect;  } 

The GetValue() Method

Use the GetValue() method to return an AnsiString representation of the value of the property being edited. To do this, use one of the Get Xxx Value() methods from the TPropertyEditor class, where Xxx will be one of Float , Int64 , Method , Ord , Str , or Var . These are listed in Table 5.5.

Table 5.5. TPropertyEditor Get Xxx Value() Methods

Method

Description

GetFloatValue()

Returns an Extended value, in other words a long double . Used to retrieve floating-point property values, such as float , double , and long double .

GetInt64Value()

Returns an __int64 value. Used to retrieve Int64 ( __int64 ) property values.

GetMethodValue()

Returns a TMethod structure:

struct TMethod { void *Code; void *Data; };

Used to retrieve Closure property values, in other words, events.

GetOrdValue()

Returns an int value. Used to retrieve Ordinal property values such as char , signed char , unsigned char , int , unsigned , short , and long . Can also be used to retrieve a pointer value; the int must be cast to the appropriate pointer using reinterpret_cast .

GetStrValue()

Returns an AnsiString value. Used to retrieve string ( AnsiString ) property values.

GetVarValue()

Returns a Variant by value. Used to retrieve Variant property values. The Variant class models Object Pascal's intrinsic variant type. Refer to the online help for a description of Variant s.

The following code shows an implementation of the GetValue() method to retrieve the value of a char -based property by calling the GetOrdValue() method.

 AnsiString __fastcall TExamplePropertyEditor::GetValue()  {     char ch = static_cast<char>(GetOrdValue());     if(ch > 32 && ch < 128) return ch;     else return AnsiString().sprintf("#%d", ch);     // Note the '#' character is pre-pended to characters     // that cannot be displayed directly. This is how the     // VCL displays non-printable character values, for     // example #8 is the backspace character (\b).  } 

Notice the use of static_cast to cast the returned int value as a char . The casting operators are often used when overriding the GetValue() and SetValue() methods of TPropertyEditor . It is essential that their proper use be understood .

The SetValue() Method

Use the SetValue() method to set a property's actual value by converting the AnsiString representation to a suitable format. To do this, use one of the Set Xxx Value() methods from TPropertyEditor , where Xxx will be one of Float , Int64 , Method , Ord , Str , or Var . These are listed in Table 5.6.

Table 5.6. TPropertyEditor Set Xxx Value() Methods

Method

Sets

SetFloatValue()

Pass an Extended ( long double ) value as an argument. Used to set floating-point property values, namely float , double , and long double .

SetInt64Value()

Pass an __int64 value as an argument. Used to set Int64 ( __int64 ) property values.

SetMethodValue()

Pass a TMethod structure as an argument. Used to set Closure (event) property values.

SetOrdValue()

Pass an int value as an argument. Used to set Ordinal property values, namely char , signed char , unsigned char , int , unsigned , short , and long . It can also be used to set pointer property values, though the pointer value must first be cast to an int using reinter pret_cast .

SetStrValue()

Pass an AnsiString as an argument. Used to set string ( AnsiString ) property values.

SetVarValue()

Pass a Variant as an argument. Used for variant ( Variant ) property values.

SetValue() should ensure that values passed to it are valid before calling one of the Set Xxx Value() methods, and it should raise an exception if this is not the case. The EPropertyError exception is sensible to use or serve as a base class from which to derive your own exception class. Sample code for an int property is shown in the following, where a value of less than zero is not allowed:

 void __fastcall     TExamplePropertyEditor::SetValue        (const AnsiString Value)  {     if(Value.ToInt() < 0)     {        throw EPropertyError("The value must be greater than 0");     }     else SetOrdValue(Value.ToInt());  } 

The Edit() Method

The Edit() method is generally used to offer a better interface to the user. Often this is a form behaving as a dialog. The Edit() method can also call GetValue() and SetValue() or even call the Get Xxx Value() and Set Xxx Value() methods. It should be noted at this point that TPropertyEditor (and derived classes) has a property called Value whose read and write methods are GetValue() and SetValue() , respectively. Its declaration is

 __property AnsiString Value = {read=GetValue, write=SetValue}; 

This can be used instead of calling GetValue() and SetValue() directly. Regardless of how GetValue() and SetValue() are called, the Edit() method should be able to display a suitable form to allow intuitive editing of the property's value.

Two basic approaches can be taken. The first is to allow the form to update the property's value while it is displayed. The second is to use the form as a dialog to retrieve the desired value or values from the user, and then set the property's value when the form returns a modal result of mrOK upon closure. Which of the two approaches is taken affects the code that appears in the Edit() method.

Now consider the first instance in which the form will continually update the value of the property. There are two basic types of property value: one that represents a single entity, such as an int , and one that represents a collection of values, such as the class TFont (though the property editor for TFont behaves according to the second approach). The difference between the two is in how Value is used to update the property. In a class property, Value is a pointer. For the form to be able to update the property, it must have the address of Value or whatever Value points to. For a class property this is simple; the pointer to the class is read from Value , and the class's values are edited through that pointer. A convenient way to do this is to declare a property of the same type as the property to be edited. This can then be equated to Value before the form is shown, allowing initial values to be displayed and stored.

In a single entity, a reference to Value should be passed in the form's constructor. Using a reference to Value ensures that each time it is modified the GetValue() and SetValue() methods are called. The only other consideration for this approach is that it is probably a good idea to store the value or values that the property had when the form was originally shown. This allows the edit operation to be cancelled and any previous value or values restored. Suitable code for these situations is shown in Listings 5.3 and 5.4, for a class property and a single entity property, respectively.

Listing 5.3 Code for a Custom Form to Be Called from the Edit() Method for a Class Property
 // First show important code for TMyPropertyForm  // IN THE HEADER FILE  //---------------------------------------------------------------------------//  #ifndef MyPropertyFormH  #define MyPropertyFormH  //---------------------------------------------------------------------------//  #include <Classes.hpp>  #include <Controls.hpp>  #include <StdCtrls.hpp>  #include <Forms.hpp>  #include "HeaderDeclaringTPropertyClass"  //---------------------------------------------------------------------------//  class TMyPropertyForm : public TForm  {  __published:    // IDE-managed Components  private:     TPropertyClass* FPropertyClass;     // Other decalrations here for example restore values if 'Cancel'     // is pressed  protected:     void __fastcall SetPropertyClass(TPropertyClass* Pointer);  public:     __fastcall TMyPropertyForm(TComponent* Owner);     __property TPropertyClass* PropertyClass = {read=FPropertyClass,                                                write=SetPropertyClass};     // Other declarations here  };  //---------------------------------------------------------------------------//  #endif  // THE IMPLEMENTATION FILE  //---------------------------------------------------------------------------//  #include <vcl.h>  #pragma hdrstop  #include "MyPropertyForm.h"  //---------------------------------------------------------------------------//  #pragma package(smart_init)  #pragma resource "*.dfm"  //---------------------------------------------------------------------------//  __fastcall TMyPropertyForm::TMyPropertyForm(TComponent* Owner)          : TForm(Owner)  {  }  //---------------------------------------------------------------------------//  void __fastcall TMyPropertyForm::SetPropertyClass(TPropertyClass* Pointer)  {     FPropertyClass = Pointer;     if(FPropertyClass != 0)     {        // Store current property values     }  }  //---------------------------------------------------------------------------//  // NOW SHOW THE Edit() METHOD  #include "MyPropertyForm.h" // Remember this  void __fastcall TExamplePropertyEditor::Edit(void)  {     // Create the form     std::auto_ptr<TMyPropertyForm*>        MyPropertyForm(new TMyPropertyForm(0));     // Link the property     MyPropertyForm->PropertyClass                     = reinterpret_cast<TPropertyClass*>(GetOrdValue());     // Show the form. The form does all the work.     MyPropertyForm->ShowModal();  }  //---------------------------------------------------------------------------//  

Notice the use of reinterpret_cast to convert the ordinal ( int ) representation of the pointer to the class to an actual pointer to the class. Listing 5.4 is shorter than Listing 5.3 because only the different code is shown.

Listing 5.4 Code for a Custom Form to Be Called from the Edit() Method for an int Property
 // First show important code for TMyPropertyForm  //---------------------------------------------------------------------------//  // IN THE HEADER FILE CHANGE THE DEFINITION TO:  class TMyPropertyForm : public TForm  {  __published:    // IDE-managed Components  private:    AnsiString& Value;    int OldValue;    // Other declarations here  public:    __fastcall TMyPropertyForm(TComponent* Owner,  AnsiString& PropertyValue);    // Other declarations here  };  //---------------------------------------------------------------------------//  #endif  //---------------------------------------------------------------------------//  // IN THE IMPLEMENTATION FILE MODIFY THE CONSTRUCTOR TO:  __fastcall TMyPropertyForm::TMyPropertyForm(TComponent* Owner,                                              AnsiString& PropertyValue)          : TForm(Owner),Value(PropertyValue)  {     // Store the current property value. In this case it is an int     // so code such as this is required     OldValue = Value.ToInt();  }  //---------------------------------------------------------------------------//  // NOW SHOW THE Edit() METHOD, almost the same...  #include "MyPropertyForm.h" // Remember this  void __fastcall TExamplePropertyEditor::Edit(void)  {     // Create the form as before, but pass the extra parameter!     std::auto_ptr<TMyPropertyForm*>        MyPropertyForm(new TMyPropertyForm(0, Value));     // Show the form. The form does all the work.     MyPropertyForm->ShowModal();  }  //---------------------------------------------------------------------------// 

The difference between the second approach and the previous approach is that the value is modified after the modal form returns rather than continually modifying it while the form is displayed. This is the more common way to use a form to edit a property's value. Listing 5.5 shows the basic code required in the Edit() method.

Listing 5.5 Code for a Custom Form to Be Called from the Edit() Method with No Updating Until Closing
 #include "MyPropertyDialog.h" // Include the header for the Dialog!                                // Dialog is TMyPropertyDialog  void __fastcall TExamplePropertyEditor::Edit(void)  {     // Create the form     std::auto_ptr<TMyPropertyDialog*>        MyPropertyDialog(new TMyPropertyDialog(0));     // Set the current property values in the dialog     // MyPropertyDialog->value1 = GetValue();     // MyPropertyDialog->value2 = GetXxxValue();     // and so on...     // Show the form and see the result.     if(MyPropertyDialog->ShowModal() == IDOK)     {        // Then set the new property value(s)     }  } 

Note that TMyPropertyDialog might not be a dialog itself, but a wrapper for a dialog, similar to the standard dialog components. If this is the case, the dialog is shown by calling the wrapper's Execute() method. For more information on this method of displaying a dialog, refer to the C++Builder online help under "Making a Dialog Box a Component." In this case, such a dialog wrapper need only descend from TObject , not TComponent .

The GetValues() Method

The GetValues() method is used to populate the drop-down list of a property. This is done by successively calling Proc() and passing an AnsiString representation of the value. For example, if a series of values is desired that represents the transmission rate between a computer's communication port and an external modem, assuming the property editor had paValueList as an attribute, the GetValues() method could be written as follows :

 void __fastcall GetValues(Classes::TGetStrProc Proc)  {     Proc("300");     Proc("9600");     Proc("57600");     // and so on...  } 

Using the TPropertyEditor Properties

TPropertyEditor has four properties that can be used when writing custom property editors. One of these, Value , we have already discussed in the previous two sections. The remaining three properties are not used very often. They are described in the following list:

  • Designer ” This property is read-only and returns a pointer to the IDE's Designer interface. This is used to inform the IDE when certain events occur or to request the IDE to perform certain actions. For example, if you write your own implementation for one of the Set Xxx Value() methods, you must tell the IDE that you have modified the property. You do this by calling Designer->Modifed(); . In fact, you would call TPropertyEditor 's Modified() method, which calls the same code. TPropertyEditor 's Revert() method also uses this property. You probably will not need to use this property. It is shown for completeness.

  • PrivateDirectory ” This property is a directory, represented as an AnsiString , as returned by GetPrivateDirectory() , which itself obtains the directory from Designer->GetPrivateDirectory() . Hence, we can see that this directory is specified by the IDE. If your property editor requires a directory to store additional files, it should be the directory specified by this property. This property is read-only.

  • PropCount ” This property is read-only and returns the number of properties being edited when more than one component is selected. It is only used when GetAttributes() returns paMultiSelect .

Considerations When Choosing a Suitable Property Editor

Consider a property in a component that wraps the Windows communication API and allows different baud rates to be set. The values that can be chosen are predetermined, but a user-defined baud rate may be specified. What is the best way to enter such values?

It would be nice to have a drop-down list of choices. It would also be nice if the values in the drop-down list were numbers , not enumerations. The first thought that springs to mind is a custom property editor that descends from TintegerProperty , but displays a drop-down list of the values that can be set. A user-defined value could be entered in the editing region of the property value in the Object Inspector. This is trivial to implement and will work fine.

Have we really thought about whether this is the best approach? Let's think again. All is well when a value from the drop-down list is chosen, but we must detect when a user-defined value is entered. This is relatively simple, but requires that all values in the list be compared with the value returned by the property. If it is different, it is a user-defined baud rate. The component must then request a user-defined baud rate from the communication API equal to the value entered. Some values might be too big or too small. We must, therefore, perform bounds checking each time a value is entered. Our property editor is simple, but we have to write an increasing amount of maintenance code to support it. Not only that, but all these problems will be revisited by the runtime code.

We could restrict the values allowable to only those in the drop-down list by overriding the SetValue() method, and then creating two separate properties: one to enter a user-defined baud rate, and a Boolean property to indicate which we want to use. It seems that we are doing an awful lot of code writing just to enter a simple integer. Let's go back to the start and look at our original requirements.

We want to be able to enter a value from a given list of possible values, and we want to be able to specify a user-defined value, which may not be acceptable. Our initial thought was probably to use an enumeration for the values, but the convenience of using actual integer values made that option seem more attractive. Let's look at the enumeration route. A set of values is easily generated; we can even ensure that they appear in numerical order in the drop-down list by using underscores between the enumeration initials and the value. For example, given an enum called TBaudRate with the initials br , the baud rates 9600 and 115200 could be represented as br___9600 and br_115200 , respectively.

We can even add a brUserDefined value to the enum . When brUserDefined is selected, an int UserDefined property can be read and the value tried. Therefore, we need this property as well. To do all this, we don't need to create a custom property editor at all since TEnumProperty is already defined as an editor for enum -based properties. We have a problem though: Any time we want to set or get a value at runtime, we must use the enumeration, which is often inconvenient. We must make this enumeration available to the component user. In the interest of keeping the global namespace clean, we could wrap the enum in a namespace, but this will make the enum even more of a hassle to use, so we won't do that. In fact, most components don't do this either. That is why initials are used in the enum 's values.

So, which is best? It all depends on exactly what is required of the property and the component as a whole. Because this is a hypothetical discussion, it is hard to choose which method is better. The one thing to remember is that you must make your components robust and easy to use. Overly complex code should be avoided especially , because it might hide some of the more subtle features of how your component works. The enumeration approach might be a bit of a hassle as you convert to and from int values, but everyone knows what you can and cannot do with them. The time you save on not having to write a custom property editor could be used elsewhere. Remember also that if you need to read a value, you can simply create a read-only property so that, for example, the int value of the baud rate could be stored when it is successfully set by the enum property. This then could be read from an int -based read-only property.

Always think carefully when you are writing property editors and components in general. Consider the big picture and think ahead.


   
Top


C++ Builder Developers Guide
C++Builder 5 Developers Guide
ISBN: 0672319721
EAN: 2147483647
Year: 2002
Pages: 253

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net