Código en Delphi 6, válido para Delphi for win32 y CodeGear RAD Studio (Delphi win32)
Home
Download
Documentos
Links
Historial
Correo

Editor de propiedades


Es este artículo vamos a ver como crear editores de propiedades. Para ello nos basaremos en dos editores que se utilizan en algunos de los componentes que podéis descargaros de esta página : TFieldsProperty y TFormatMaskProperty, estos se utilizan para editar propiedades de componentes como TExcelExport, TFieldsType, TLog, etc.

Antes de nada y como ya se desprende del párrafo anterior, un editor de propiedades va a servir para editar propiedades de un componente en el inspector de objetos de delphi. El inspector de objetos sólo trabaja con cadenas de caracteres, aunque las propiedades sean integer o de cualquier otro tipo, así que nuestro editor deberá hacer las correspondientes transformaciones de datos. A parte de la edición de propiedades, el editor que creemos nos podrá servir, por ejemplo, para acotar posibles valores, elección de un valor entre varios, mostrar cajas de diálogo para hacer más fácil de entrada de datos, etc.

Delphi define una serie de editores por defecto, los cuales servirán como base de los que podamos crear, es decir, nuestros editores tendrán como ancestro a alguno de los ya existentes y por lo tanto heredarán todas las propiedades y métodos de estos.

Los editores definidos por delphi (en su versión 6) en DesignEditors.pas son :

Editor

Descripción

TOrdinalProperty

Clase base para todos los editores de propiedades de ordinales (Enteros, enumerados,etc).
TIntegerProperty
Editor para enteros de cualquier tamaño.
TCharProperty
Para tipos Char y cualquier subrango de Char.
TEnumProperty
Tipos enumerados definidos por el usuario.
TFloatProperty
Para propiedades de coma flotante.
TStringProperty
Cadenas de caracteres.
TSetElementProperty
Para cada elemento de un conjunto (set). Cada elemento se muestra como un Boolean.
TSetProperty
Editor por defecto para conjunto de elementos (set).
TClassProperty
Editor para propiedades que son a su vez objetos.
TMethodProperty
Editor para propiedades que son punteros a métodos, es decir, eventos.
TComponentProperty
Editor para propiedades que se refieren a un componente. No es lo mismo que TClassProperty. En este caso el inspector de objetos permite al usuario especificar el componente a que se refiere (es el caso de la propiedad ActiveControl).
TColorProperty
Editor para Tcolor.
TFontNameProperty
Editor para nombre de fuentes. Muestra una lista desplegable con las fuentes.
TFontProperty
Editor de TFont.
TInt64Property
Editor para Int64 y derivados.
TNestedProperty
El editor utiliza el editor de propiedades del padre.
TInterfaceProperty
Editor para referencias de interfaces.
TComponentNameProperty
Editor para la propiedad Name
TDateProperty
Editor de la fecha de un TDateTime
TTimeProperty
Editor de la hora de un TDateTime.
TDateTimeProperty
Editor de TDateTime.
TVariantProperty
Editor de tipos Variants

Entonces dependiendo de la funcionalidad que queramos que tenga haremos que nuestro editor descienda de alguno de estos. En nuestro caso vamos a heredar de TStringProperty para el editor de TFormatMask y de TClassProperty para el editor de TFields. En el primer caso porque la propiedad será una cadena de caracteres y en el segundo una clase.

Editor TFormatMaskProperty

Antes de nada decir que TFormatMask es una clase definida como Type String que contendrá una cadena de formato de fecha/hora que se utilizará en el componente TLog (puedes bajarlo de www.i-griegavcl.com). Para hacer más fácil la introducción de una máscara correcta y dar la posibilidad de tener unos valores predefinidos, vamos a hacer un editor de propiedades que mostrará una caja de diálogo para llevar a cabo todo esto.

Como hemos dicho para hacer el editor para la propiedad TFormatMask heredamos de TStringProperty. La definición de esta clase es :

TStringProperty = class(TPropertyEditor)
  public
    function AllEqual: Boolean; override;
    function GetEditLimit: Integer; override;
    function GetValue: string; override;
    procedure SetValue(const Value: string); override;
end;      

Los procedimientos GetValue y SetValue que sirven para obtener y de devolver el valor de la propiedad del inspector de objetos. Están definidas de la siguiente forma :

 
function TStringProperty.GetValue: string;
begin
  Result := GetStrValue;
end;

procedure TStringProperty.SetValue(const Value: string);
begin
  SetStrValue(Value);
end;

GetStrValue y SetStrValue son métodos definidos en TPropertyEditor que lo único que hacen es recuperar o asignar el valor de la propiedad como una cadena de caracteres.

Como primer paso vamos a ver la definición del editor y después pasaremos a explicar paso a paso cada parte :

 
    TFormatMaskProperty = class(TStringProperty)
       public
          procedure Edit; override;
          function GetAttributes: TPropertyAttributes; override;
    end;
      

El procedimiento Edit servirá para editar la propiedad y la funcion GetAttributes le informará al inspector de objetos sobre atributos especiales a tener en cuenta para editar la propiedad, estos atributos pueden ser los siguientes :

Atributo

Descripción

paValueList

Devuelve una lista de valores enumerados. Para rellenar la lista se utiliza GetValues(). (p.e. BorderStyle de Tform)
paSubProperties La propiedad tiene subpropiedades que el inspector de objetos debe mostrar identadas, por ejemplo Tfont. El atributo paValueList es obligatorio en este caso.
paDialog El inspector de objetos muestra un botón ellipsis(...) para invocar al metodo Edit del editor de propiedades que se encargará de mostrar una caja de diálogo.
paMultiSelect La propiedad debe ser mostrada cuando se seleccionan varios componentes en el ‘Form Designer’, esto permite cambiar el valor de una propiedad de varios componentes a la vez.
paAutoUpdate El método SetValue() se llama cada vez que se produce un cambio en la propiedad, en caso contrario sólo se llama cuando se pulsa enter o nos movemos a otra propiedad del inspector de objetos.
paFullWidthName El valor no necesita ser mostrado. El inspector de objetos utiliza todo el ancho para mostrar el nombre de la propiedad.
paSortList La lista mostrada por GetValues() aparecerá ordenada.
paReadOnly El valor de la propiedad no puede ser modificado.
paRevertable La propiedad puede revertir a su valor original. Hay propiedades de las que no se puede recuperar el valor original como es el caso de TFont.

Nosotros redefiniremos el procedimiento Edit y la Función GetAttributes para hacer que el editor muestre una caja de diálogo que nos permitir introducir la máscara. La implementación será :

 
   function TFormatMaskProperty.GetAttributes: TPropertyAttributes;
   begin
      Result := [paDialog];
   end;

Le indicamos al inspector de objetos que queremos que llame al método Edit de nuestro editor de propiedades [paDialog].

El procedimiento Edit será :

 
procedure TFormatMaskProperty.Edit;
var
  DatemaskEditor: TfrmDateMask;
begin
  DatemaskEditor := TfrmDatemask.Create(Application);
  try
     DatemaskEditor.txtTest.text:=GetValue;
     if DatemaskEditor.ShowModal=mrok then
     begin
        SetValue(DatemaskEditor.txtTest.text);
        designer.Modified;
     end;
  finally
    DatemaskEditor.Free;
  end;
end;

Lo que hace este procedimiento es lo siguiente :

1.- Define una variable de tipo TFrmDateMask, esta clase es un form que nos servirá como caja de diálogo, más adelante veremos su definición.

2.- Crea una instancia del objeto.

3.- Recupera el valor de la propiedad (GetValue) y lo asigna a un TEdit del form de la instancia anterior.

4.- Cuando la caja (form) de diálogo se cierre pulsando el botón Ok. coge el valor del Edit anterior y lo asigna a la propiedad (SetValue).

5.- Avisa al Designer de que ha habido una modificación

6.- Libera la instancia.

En vez de utilizar los métodos GetValue y SetValue, en nuestro caso, hubiéramos podido utilizar GetStrValue y SetStrValue definidos en TPropertyEditor, aunque lo correcto es hacerlo con GetValue y SetValue.

Bueno, pues ya sólo nos falta registrar el editor para que delphi lo utilice :

RegisterPropertyEditor(TypeInfo(TFormatMask), nil, '',TFormatMaskProperty);

La definición de este procedimiento es :

procedure RegisterPropertyEditor(PropertyType: PTypeInfo; ComponentClass: TClass; const PropertyName: string; EditorClass: TPropertyEditorClass);

dónde :

PropertyType informa sobre la propiedad a la que se le aplicará el editor. Es un puntero a la RunTime Type Information (RTTI) de la propiedad a editar.

ComponentClass indica el componente que contiene la propiedad. En nuestro caso lo ponemos a nil para poder utilizar el editor para este tipo de propiedad en cualquier componente.

PropertyName indica el nombre de la propiedad. Nosotros lo ponemos como una cadena vacía para que en distintos componentes pueda tener valores distinto y no tenga que llamarse siempre igual.

EditorClass es el editor de propiedades que hemos creado.

El resultado final será el siguiente :

La definición de la caja de diálogo TfrmDateMask es la siguiente y para crearla utilice ‘New Application’ del delphi. :


DateMask.dfm
object frmDateMask: TfrmDateMask
  Left = 289
  Top = 231
  BorderIcons = []
  BorderStyle = bsDialog
  Caption = 'DateTime Mask'
  ClientHeight = 178
  ClientWidth = 383
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object lblTest: TLabel
    Left = 192
    Top = 88
    Width = 31
    Height = 13
    Caption = 'lblTest'
  end
  object FormatList: TListBox
    Left = 16
    Top = 16
    Width = 161
    Height = 145
    ItemHeight = 13
    Items.Strings = (
      'dd/mm/yyyy hh:nn:ss.zzz'
      'dd-mm-yyyy hh:nn:ss.zzz'
      'dd/mm/yyyy hh:nn:ss'
      'dd-mm-yyyy hh:nn:ss'
      'd/mmm/yyyy hh:nn:ss'
      'dd-mm-yyyy'
      'dd-mmm-yyyy'
      'dd '#39'de'#39' mmmm '#39'de'#39' yyyy')
    TabOrder = 0
    OnClick = FormatListClick
  end
  object txtTest: TEdit
    Left = 192
    Top = 16
    Width = 161
    Height = 21
    TabOrder = 1
    Text = 'dd/mm/yyyy hh:nn:ss.zzz'
  end
  object Button1: TButton
    Left = 192
    Top = 48
    Width = 41
    Height = 25
    Caption = 'Test'
    Default = True
    TabOrder = 2
    OnClick = Button1Click
  end
  object btnAceptar: TBitBtn
    Left = 200
    Top = 136
    Width = 75
    Height = 25
    TabOrder = 3
    Kind = bkOK
  end
  object btnCancelar: TBitBtn
    Left = 296
    Top = 136
    Width = 75
    Height = 25
    Caption = 'Cancelar'
    TabOrder = 4
    Kind = bkCancel
  end
end

DateMask.pas
unit DateMask;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons;

type
  TfrmDateMask = class(TForm)
    FormatList: TListBox;
    lblTest: TLabel;
    txtTest: TEdit;
    Button1: TButton;
    btnAceptar: TBitBtn;
    btnCancelar: TBitBtn;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormatListClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmDateMask: TfrmDateMask;

implementation

{$R *.dfm}

procedure TfrmDateMask.FormCreate(Sender: TObject);
begin
   lblTest.Caption:=formatdatetime(txtTest.Text,now);
end;

procedure TfrmDateMask.Button1Click(Sender: TObject);
begin
   if txtTest.Text<>'' then
      lblTest.Caption:=formatdatetime(txtTest.Text,now);
end;

procedure TfrmDateMask.FormatListClick(Sender: TObject);
begin
   txtTest.Text:=FormatList.Items[FormatList.itemindex];
end;

end.

Editor TFieldsPropery.

Este editor lo utilizaremos para editar la propiedad Fields (TFieldsList) del componente TFieldsType. Como esta propiedad es un StringList, utilizaremos como ancestro de nuestro editor la clase TClassProperty puesto que tendremos que editar una propiedad que es a su vez una clase (TFieldsList).

Al igual que en el editor anterior, decir que la clase TFieldsType permite seleccionar un subconjunto de campos del conjunto total de una tabla. Su definición es :

 TFieldsType=Class(Tcomponent)
   private
      FTable:tdataset;
      FFields:TFieldsList;
   published
      property Table:tdataset read FTable Write FTable;
      property Fields:tFieldslist read FFields write FFields;
   public
      constructor Create(AOwner: TComponent);override;
      destructor destroy;override;
      procedure SetAllFields;
      function Locate(cad:string;var Index:integer):Boolean;
      procedure Edit;
end;

A su vez TFieldsList tiene la siguiente definición :

TFieldsList=class(TStringlist)
   public
      function Locate(cad:string;var Index:integer):Boolean;
end;

Y pasando al editor de propiedades que vamos a crear, lo primero mostrar como lo vamos a definir y después pasaremos a explicar porque hacemos las cosas como las hacemos :

TFieldsProperty = class(TClassProperty)
  public
    procedure Edit; override;
    function GetAttributes: TPropertyAttributes; override;
end;

Como podemos observar la definición es la misma que la del editor anterior, la única diferencia es de que clase heredamos, bueno esa y la implementación que lógicamente será distinta.

function TFieldsProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paDialog];
end;      

Esta función es en todo igual a la del editor anterior, así que vale la explicación también para éste.

procedure TFieldsProperty.Edit;
var
  FieldsEditor: TfrmFields ;
  i,index:integer;

begin
  FieldsEditor := TfrmFields.Create(Application);
  try
     if TFieldsType(Getcomponent(0)).fields.Count>0 then
     begin
        fieldseditor.L2.Clear;
        fieldseditor.L1.Clear;
        TFieldsType(Getcomponent(0)).table.FieldDefs.update;
        for i:=0 to TFieldsType(Getcomponent(0)).table.FieldDefs.count -1 do
        begin
           if TFieldsType(Getcomponent(0)).fields.Locate( 
                                  TFieldsType(Getcomponent(0)).table.
                                          FieldDefs[i].Name,index) then
              fieldseditor.L2.Items.Add( 
                         TFieldsType(Getcomponent(0)).table.FieldDefs[i].Name)
           else
              fieldseditor.L1.Items.Add(
                         TFieldsType(Getcomponent(0)).table.FieldDefs[i].Name);
        end;
     end
     else
     begin
        TFieldsType(Getcomponent(0)).setallfields;
        fieldseditor.L2.Items.Assign(TFieldsType(Getcomponent(0)).fields);
     end;
     if FieldsEditor.ShowModal=mrok then
     begin
        TFieldsType(Getcomponent(0)).fields.clear;
        TFieldsType(Getcomponent(0)).fields.Assign(fieldseditor.L2.Items);
        designer.Modified;
     end;
  finally
    FieldsEditor.Free;
  end;
end;      

Los pasos seguidos son los mismos que en el editor de TFormatMaskProperty : Crear una instancia de una caja de diálogo, asignar valores según los valores contenidos en la propiedad, si Ok. asignar los valores adecuados de la caja de diálogo a la propiedad en el inspector de objetos y liberar la instancia. Lo nuevo es el método GetComponent(0) definido en TPropertyEditor. Este método nos sirve para acceder al objeto cuya propiedad estamos editando, de esta manera podremos tener acceso a otras propiedades del objeto, en nuestro caso necesitamos saber de que tabla extraer los nombres de los campos. El parámetro que se le pasa indica el índice del objeto puesto que el editor puede manejar varios objetos a la vez.

Puesto que este método nos devuelte un puntero de TPersistent, debemos hacer uso del polimorfismo de los objetos haciendo un ‘cast’ : TFieldsType(Getcomponent(0)), de esta manera ya tenemos acceso a la propiedad ‘Table’ del objeto : TFieldsType(Getcomponent(0)).table.

El resto es programación pura y dura y fácil de entender.

Bueno, pues sólo nos queda registrar el editor y como en el caso anterior tendremos que llamar al procedimiento RegistrerPropertyEditor :

RegisterPropertyEditor(TypeInfo(TFieldslist), TFieldsType, '',TFieldsProperty);

"Vaya", hemos encontrado otra diferencia con el editor TFormatMaskProperty, a saber, esta vez sí que pasamos el parámetro ComponentClass: (TFieldsType), y esto ¿por qué?, pues la razón es sencilla, la propiedad que estamos editando sólo tiene sentido si está en la clase TFieldsType, así que nos curamos en salud si sólo dejamos utilizar el editor para esta propiedad en esta clase.

La definición de TfrmFields utilizado en el procedimiento Edit del editor es la siguiente :


Fields.dfm
object frmFields: TfrmFields
  Left = 365
  Top = 229
  Width = 304
  Height = 275
  Caption = 'Campos a exportar'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 3
    Top = 2
    Width = 74
    Height = 13
    Caption = 'Disponibles :'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = [fsBold]
    ParentFont = False
  end
  object Label2: TLabel
    Left = 171
    Top = 2
    Width = 58
    Height = 13
    Caption = 'Actuales :'
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'MS Sans Serif'
    Font.Style = [fsBold]
    ParentFont = False
  end
  object L1: TListBox
    Left = 0
    Top = 17
    Width = 121
    Height = 185
    ItemHeight = 13
    TabOrder = 0
  end
  object L2: TListBox
    Left = 168
    Top = 17
    Width = 121
    Height = 185
    ItemHeight = 13
    TabOrder = 1
  end
  object B1: TButton
    Left = 128
    Top = 49
    Width = 33
    Height = 25
    Caption = '>>'
    TabOrder = 2
    OnClick = B1Click
  end
  object B2: TButton
    Left = 128
    Top = 81
    Width = 33
    Height = 25
    Caption = '>'
    TabOrder = 3
    OnClick = B2Click
  end
  object B3: TButton
    Left = 128
    Top = 113
    Width = 33
    Height = 25
    Caption = '<'
    TabOrder = 4
    OnClick = B3Click
  end
  object B4: TButton
    Left = 128
    Top = 145
    Width = 33
    Height = 25
    Caption = '<<'
    TabOrder = 5
    OnClick = B4Click
  end
  object btnAceptar: TBitBtn
    Left = 48
    Top = 215
    Width = 75
    Height = 25
    Caption = '&Aceptar'
    TabOrder = 6
    Kind = bkOK
  end
  object btnCancelar: TBitBtn
    Left = 168
    Top = 215
    Width = 75
    Height = 25
    Caption = '&Cancelar'
    TabOrder = 7
    Kind = bkCancel
  end
end

Fields.pas
unit fields;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Buttons, StdCtrls;

type
  TfrmFields = class(TForm)
    L1: TListBox;
    L2: TListBox;
    B1: TButton;
    B2: TButton;
    B3: TButton;
    B4: TButton;
    btnAceptar: TBitBtn;
    btnCancelar: TBitBtn;
    Label1: TLabel;
    Label2: TLabel;
    procedure B1Click(Sender: TObject);
    procedure B4Click(Sender: TObject);
    procedure B2Click(Sender: TObject);
    procedure B3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


implementation

{$R *.DFM}


procedure TfrmFields.B1Click(Sender: TObject);
var
   i:integer;
begin
   FOR I:=0 TO L1.Items.Count-1 DO
      l2.Items.Add(l1.Items[i]);
   l1.Items.Clear;
end;

procedure TfrmFields.B4Click(Sender: TObject);
var i:integer;
begin
   FOR I:=0 TO L2.Items.Count-1 DO
      l1.Items.Add(l2.Items[i]);
   l2.Items.clear;
end;

procedure TfrmFields.B2Click(Sender: TObject);
begin
   if l1.ItemIndex>-1 then
   begin
      l2.items.Add(l1.items[l1.itemindex]);
      l1.Items.Delete(l1.itemindex);
   end;
end;

procedure TfrmFields.B3Click(Sender: TObject);
begin
   if l2.ItemIndex>-1 then
   begin
      l1.items.Add(l2.items[l2.itemindex]);
      l2.Items.Delete(l2.itemindex);
   end;
end;

end.

Hasta aquí llegó este artículo. Posiblemente en otros abordaremos otros editores, hasta que esto ocurra espero que éste os sirva en vuestras tareas de programación de componentes.