Código en Delphi 6, válido para Delphi for win32 y CodeGear RAD Studio (Delphi win32) | ||
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.
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. :
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
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.
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 :
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
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.