![]() |
![]() |
| Volver al índice | Por Luis Roche
|
En la anterior unidad aprendimos el funcionamiento básico de un editor de propiedades y desarrollamos un ejemplo de un editor de propiedades que trabajaba sobre el inspector de objetos (BinaryPropEd).
En esta unidad desarrollaremos ¡cuatro! editores de propiedades y un componente que nos servira para probar los editores.
Parece interesante, ¿verdad? Pues manos a la obra :)
Antes de comenzar a desarrollar los editores de propiedades vamos a crear un componente que nos permitirá probarlos según los completemos. Este componente lo he denominado TPrueba y su código es el siguiente:
unit Unidad9;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DsgnIntf, DB, PasswordForm;
type
TPrueba = class(TComponent)
private
FFichero : string;
FAlias : string;
FFecha : TDateTime;
FPassword : string;
protected
public
constructor Create(AOwner : TComponent); override;
published
property Fichero : string read FFichero write FFichero;
property Alias : string read FAlias write FAlias;
property Fecha : TDateTime read FFecha write FFecha;
property Password : string read FPassword write FPassword;
end;
...
implementation
...
constructor TPrueba.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FFecha:=Now;
end;
...
Nada del otro mundo. Cuatro propiedades para cada uno de los editores de propiedades a probar y un constructor de los más normal para asignar un valor por defecto a la propiedad fecha.
Lo que si conviene hacer notar es que por simplicidad desarrollaremos el componente y los cuatro editores de propiedades en una única unidad (hay una unidad adicional necesaria para el editor TPasswordProperty). Como digo, lo haremos así por simplicidad, pero no es lo más correcto. En general, cada editor de propiedades debe ir en una unidad independiente del propio componente, cómo ya vimos en la unidad 8.
Fijé en la propiedad Fichero tal y como la hemos implementado en el componente TPrueba. Por supuesto, es de tipo string. Además no hemos definido ni métodos Set ni Get, por lo que la escritura/lectura de valores en esta propiedad se hace directamente sobre el campo asociado (FFichero). Tal y como está, cuando un usuario del componente quiera introducir un valor en esta propiedad, tendrá que escribirlo a mano, lo cuál puede llegar a ser una tediosa tarea si el fichero en cuestión consta de un largo path. De modo que, generosos nosotros ;), decidimos echarle una mano: le construiremos un editor de propiedades.
Delphi ya incorporá un componente que maneja muy bien la elección de apertura de archivos: TOpenDialog, de modo que tan sólo nos queda ver cómo podemos utilizarlo en nuestro editor.
El primer paso es decidir de quién descenderá nuestro editor. En nuestro caso, ya que la propiedad fichero es de tipo string, decidimos heredar de TStringProperty. A continuación debemos decidir si la propiedad será editable en el propio editor de objetos o utilizará un cuadro de diálogo. En este caso parece bastante claro que debemos elegir la segunda opción. Por supuesto esto no quita que el usuario pueda escribir directamente el valor de la propiedad en el inspector de objetos y es que ¡masoquistas los hay en todas partes! ;)
Ahora bien, ¿cómo indicarle a Delphi que se trata de un editor tipo cuadro de diálogo? Para lograrlo, debemos utilizar otro método de la clase base de todos los editores de propiedades (TPropertyEditor): el método GetAttributes Este método es una función que determina que características tendrá el editor de propiedades. Las características deseadas debemos devolverlas como resultado de esta función; este resultado es del tipo TPropertyAttributes El tipo TPropertyAttributes es un conjunto (set) que puede tomar los siguientes valores:
| Atributo | Descripción |
| paValueList | Especifica que el editor debe mostrar una lista con los valores posibles para la propiedad. Para rellenar la lista se utiliza el método GetValues |
| paSortList | Sólo válida si se selecciona paValueList. Especifica que la lista de valores se mostrará ordenada. |
| paSubProperties | Indica que el editor define subpropiedades a mostrar identadas a la derecha (p.e. la propiedad font del componente TForm). Para generar la lista de propiedades se utiliza el método GetProperties. |
| paDialog | Indica que el editor de propiedades debe mostrar un cuadro de diálogo en respuesta al método Edit. (p.e. la propiedad glyph de un TSpeddButton). De este modo al seleccionar la propiedad aparecerá un botón con la cadena '...' para editar la propiedad. |
| paMultiSelect | Si se selecciona este atributo, la propiedad se mostrará cuando se seleccionen multiples componentes. |
| paAutoUpdate | Si se selecciona, el inspector de objetos llama al método SetValue cada vez que se cambia el valor de la propiedad. Si no se selecciona, sólo se llama al método SetValue cuando el usuario presiona Enter o sale de la propiedad |
| paReadOnly | Si se selecciona, el usuario no puede modificar el valor de la propiedad |
| paRevertable | Especifica si la propiedad puede recuperar su valor original |
A la vista de esta tabla, resulta fácil implementar el método GetAtributes en nuestro editor de propiedades:
interface
...
TFicheroProperty = class(TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure Edit; override;
end;
...
function TFicheroProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paDialog];
end;
De nuevo en aras de la simplicidad sólo activamos el atributo paDialog pero, por supuesto, podíamos activar otros atributos tales como paMultiSelect, etc. En este caso, devolveríamos [paDialog, paMultiSelect]
Sólo nos queda por saber cuando activará Delphi el cuadro de diálogo. Al haber seleccionado paDialog, cuando el usuario haga click en el botón '...' o haga doble click sobre la propiedad, Delphi invocará el método Edit de nuestro editor de propiedades. En este método debemos codificar lo necesario para mostrar el cuadro de apertura de ficheros y asignar el fichero seleccionado a la propiedad si el usuario pulsa OK. Dicho método, que debemos reimplementar (override, ver la sección de interface), queda así:
...
procedure TFicheroProperty.Edit;
var
OpenDialog : TOpenDialog; {TOpenDialog está en la unidad Dialogs, no ovidar añadir a la clausula uses}
begin
OpenDialog:=TOpenDialog.Create(Application); {Creamos el cuadro de diálogo}
try
OpenDialog.Filter:='All files|*.*'; {Asignamos sus propiedades iniciales}
if OpenDialog.Execute then {Si el usuario pulsa OK...}
SetStrValue(OpenDialog.FileName); {...asignamos el nuevo valor a la propiedad}
finally
OpenDialog.Free; {Liberamos el cuadro de diálogo}
end;
end;
...
Más fácil imposible. Tan sólo comentar que nos aseguramos de la liberación de recursos (en este caso del cuadro de diálogo) mediente el uso de la construcción try..finally
¡Ya está! Con sólo 10 o 15 líneas de código hemos aliviado el sufrimiento del pobre usuario que se dejaba los dedos escribiendo nombres de ficheros. ¡Si es que somos unos santos! ;) Sólo nos queda registrarlo con la sentencia:
procedure Register; begin ... RegisterPropertyEditor(TypeInfo(string),TPrueba,'Fichero',TFicheroProperty); ... end;
Construyamos a continuación un editor de propiedades para la propiedad Alias. Cómo ya sabemos, el componente TTable tiene una propiedad denominada DatabaseName que especifica el alias de la base de datos al que está conectada la tabla. Para seleccionar un valor para esta propiedad existe una lista desplegable que le muestra al usuario todos los alias disponibles. Este es el comportamiento que queremos para nuestra propiedad Alias. Podríamos buscar por el código fuente de la VCL y tratar de registrar el editor de propiedades de la propiedad DatabaseName para que incluya también nuestra nueva propiedad, pero cómo es muy sencillo la construcción de un editor así, vamos a desarrollarlo nosotros mismos.
La sección de interface de nuestro editor es la asiguiente:
...
TAliasProperty = class (TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure GetValues(Proc : TGetStrProc); override;
end;
...
El método GetAttributes lo hemos conocido en la sección anterior. Nos basta con devolver los valores paValueList para indicar que el editor de propiedad será editable desde el propio inspector de objetos en forma de lista de valores y paSortList para que nos muestre los diversos alias existentes ordenados alfabeticamente.
function TAliasProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paValueList, paSortList];
end;
La tarea que nos queda ahora es llenar la lista con los diversos alias disponibles. Para ello, reimplementaremos (override) el método GetValues. Este método recibe un único parámetro: un puntero a método. Realmente este puntero referencia el método interno Add para el string list interno utilizado para llenar la lista desplegable. Los diversos elementos se añaden a la lista en el método GetValues invocando el método referenciado por el puntero al método y convirtiendolo en un valor tipo string. Suena complicado, ¿verdad? No os preocupeis, que no lo es tanto, tan sólo significa que hay que añadir una sentencia del tipo Proc(valor string) por cada elemento a añadir a la lista. En nuestro caso, como queremos añadir los nombres de los alias existentes, haremos un bucle que hará una llamada a Proc(nombre de alias) para ir añadiendo todos los valores. Previamente, habremos obtenido los alias existentes mediante el método GetAliasList del objeto TSession:
procedure TAliasProperty.GetValues(Proc : TGetStrProc);
Var
AliasList : TStringList; {lista con los alias existentes}
i : integer;
begin
try
AliasList := TStringList.Create; {Creamos la lista}
Session.GetAliasNames(AliasList); {Obtenemos los alias existentes}
for i:=0 to AliasList.Count - 1 do {Por cada alias...}
Proc(AliasList[i]); {...hacemos la llamada al método Proc}
finally
AliasList.Free; {Liberamos la lista}
end;
end;
Con esto ya hemos construido nuestro nuevo editor de propiedades. Ya sólo nos falta registrarlo:
procedure Register; begin ... RegisterPropertyEditor(TypeInfo(String),TPrueba,'Alias',TAliasProperty); ... end;
Construyamos ahora un nuevo editor de propiedades de tipo cuadro de diálogo. Su funcionamiento será similar al editor TFicheroProperty. Devolveremos paDialog para indicar que se trata de un editor de tipo cuadro de diálogo y en el método Edit codificaremos las sentencias necesarias para el proceso del dicho cuadro de diálogo.
La principal diferencia radica en que no utilizaremos un cuadro de diálogo prediseñado, si no que utilizaremos uno de fabricación casera. Para ello, debemos crear un form y añadirle dos labels, dos cuadros de edición y dos bitbuttons. He aqí una imagen de este form:
Lo más importante es asignar a la propiedad PasswordChar de los dos TEdit, denominados PW1 y PW2, el caracter '*' para que dicho caracter aparezca cuando un usuario teclea una contraseña. Además, en respuesta al evento OnCloseQuery comprobaremos la validez de la contraseña introducida.
El código de la unidad PasswordForm queda así:
unit PasswordForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;
type
TfrmPassword = class(TForm)
lpwd: TLabel;
lVpwd: TLabel;
PW1: TEdit;
PW2: TEdit;
bOK: TBitBtn;
bCancel: TBitBtn;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
public
end;
var
frmPassword: TfrmPassword;
implementation
{$R *.DFM}
procedure TfrmPassword.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
begin
if (ModalResult=mrOK) then
if (PW1.Text = '') then
begin
ShowMessage('Debe introducir una contraseña');
CanClose:=False;
end
else if (ModalResult=mrOK) and (PW1.Text <> PW2.Text) then
begin
ShowMessage('Verificación fallida. Por favor reintente');
CanClose:=False;
end;
end;
end.
Ya hemos construido el cuadro de diálogo, ahora sólo nos resta "engancharlo" al editor de propiedades. Para ello, efectuamos la llamada al form en el método Edit del editor de propiedades. Veamos como queda el código del editor:
function TPasswordProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paDialog];
end;
function TPasswordProperty.GetValue : string;
begin
Result:=Format('(%s)',[GetPropType^.Name]);
end;
procedure TPasswordProperty.Edit;
begin
frmPassword := TfrmPassword.Create(Application);
try
frmPassword.Caption:=GetComponent(0).Owner.Name+'.'+
GetComponent(0).Name+'.'+GetName+' - '+
frmPassword.Caption;
frmPassword.PW1.Text:=GetStrValue;
frmPassword.PW2.Text:=frmPassword.PW1.Text;
if frmPassword.ShowModal = mrOK then
SetStrValue(frmPassword.PW1.Text)
finally
frmPassword.Free;
end;
end;
Sólo hay una cosa nueva: en el método GetValue no queremos que se nos muestre el valor de la contraseña, ya que entonces ¿para que nos hemos tomado tantas molestias? De modo que debemos mostrar otra cosa. Podría ser una cadena de asteriscos, pero un convenio utilizado en estos casos es mostrar el tipo de la propiedad, en este caso un string. Dicho esto, sólo nos queda registrar nuestro tercer editor de propiedades:
procedure Register; begin ... RegisterPropertyEditor(TypeInfo(String),TPrueba,'Password',TPasswordProperty); end;
Para terminar esta unidad desarrollaremos, a petición de algunos seguidores del curso, un editor de propiedades tipo TDateTime.
Como ya sabemos el tipo TDateTime de Delphi es en realidad un float, de modo que si tenemos en un componente una propiedad de tipo DateTime, y no registramos ningún editor de propiedades para dicha propiedad, el usuario tendrá que escribir la fecha deseada en formato decimal. ¡Qué mal! Sobre todo si pensamos que con seis líneas de código el problema queda solucionado:
function TDateTimeProperty.GetValue : string; begin Result:=DateTimeToStr(GetFloatValue); end; procedure TDateTimeProperty.SetValue(const Value : string); begin SetFloatValue(StrToDateTime(Value)); end; procedure Register; begin ... RegisterPropertyEditor(TypeInfo(TDateTime),TPrueba,'Fecha',TDateTimeProperty); end;
Nada nuevo aquí. Tan sólo nos limitamos a llamar a los métodos GetFloatValue y SetFloatValue según nos interesa
Con esto damos por terminado el tema de los editores de propiedades. Hemos aprendido a crear editores editables sobre el inspector de objetos, bien sea directamente o mediante una lista desplegable, así como editores de tipo cuadro de diálogo. La potencia de un editor de propiedades es inmensa: mediante los méetodos SetValue y GetValue podemos efectuar una verificación del valor de la propiedad y actuar en consecuencia. Además, nuestro editor puede hacer mil cosas: consultar un fichero ini, efectuar complejos cálculos o ¡incluso operar con una base de datos!
Pero los editores de propiedades tienen dos limitaciones:
unit Unidad9;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DsgnIntf, DB, PasswordForm;
type
TPrueba = class(TComponent)
private
FFichero : string;
FAlias : string;
FFecha : TDateTime;
FPassword : string;
protected
public
constructor Create(AOwner : TComponent); override;
published
property Fichero : string read FFichero write FFichero;
property Alias : string read FAlias write FAlias;
property Fecha : TDateTime read FFecha write FFecha;
property Password : string read FPassword write FPassword;
end;
TFicheroProperty = class(TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure Edit; override;
end;
TAliasProperty = class (TStringProperty)
public
function GetAttributes : TPropertyAttributes; override;
procedure GetValues(Proc : TGetStrProc); override;
end;
TDateTimeProperty = class(TFloatProperty)
function GetValue : string; override;
procedure SetValue(const Value : string); override;
end;
TPasswordProperty = class(TPropertyEditor)
function GetAttributes : TPropertyAttributes; override;
function GetValue : string; override;
procedure Edit; override;
end;
procedure Register;
implementation
constructor TPrueba.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
FFecha:=Now;
end;
function TFicheroProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paDialog];
end;
procedure TFicheroProperty.Edit;
var
OpenDialog : TOpenDialog;
begin
OpenDialog:=TOpenDialog.Create(Application);
try
OpenDialog.Filter:='All files|*.*';
if OpenDialog.Execute then
SetStrValue(OpenDialog.FileName);
finally
OpenDialog.Free;
end;
end;
function TAliasProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paValueList, paSortList];
end;
procedure TAliasProperty.GetValues(Proc : TGetStrProc);
Var
AliasList : TStringList;
i : integer;
begin
try
AliasList := TStringList.Create;
Session.GetAliasNames(AliasList);
for i:=0 to AliasList.Count - 1 do
Proc(AliasList[i]);
finally
AliasList.Free;
end;
end;
function TDateTimeProperty.GetValue : string;
begin
Result:=DateTimeToStr(GetFloatValue);
end;
procedure TDateTimeProperty.SetValue(const Value : string);
begin
SetFloatValue(StrToDateTime(Value));
end;
function TPasswordProperty.GetAttributes : TPropertyAttributes;
begin
Result:=[paDialog];
end;
function TPasswordProperty.GetValue : string;
begin
Result:=Format('(%s)',[GetPropType^.Name]);
end;
procedure TPasswordProperty.Edit;
begin
frmPassword := TfrmPassword.Create(Application);
try
frmPassword.Caption:=GetComponent(0).Owner.Name+'.'+
GetComponent(0).Name+'.'+GetName+' - '+
frmPassword.Caption;
frmPassword.PW1.Text:=GetStrValue;
frmPassword.PW2.Text:=frmPassword.PW1.Text;
if frmPassword.ShowModal = mrOK then
SetStrValue(frmPassword.PW1.Text)
finally
frmPassword.Free;
end;
end;
procedure Register;
begin
RegisterComponents('Pruebas', [TPrueba]);
RegisterPropertyEditor(TypeInfo(string),TPrueba,'Fichero',TFicheroProperty);
RegisterPropertyEditor(TypeInfo(String),TPrueba,'Alias',TAliasProperty);
RegisterPropertyEditor(TypeInfo(TDateTime),TPrueba,'Fecha',TDateTimeProperty);
RegisterPropertyEditor(TypeInfo(String),TPrueba,'Password',TPasswordProperty);
end;
end.
unit PasswordForm;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;
type
TfrmPassword = class(TForm)
lpwd: TLabel;
lVpwd: TLabel;
PW1: TEdit;
PW2: TEdit;
bOK: TBitBtn;
bCancel: TBitBtn;
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
public
end;
var
frmPassword: TfrmPassword;
implementation
{$R *.DFM}
procedure TfrmPassword.FormCloseQuery(Sender: TObject;
var CanClose: Boolean);
begin
if (ModalResult=mrOK) then
if (PW1.Text = '') then
begin
ShowMessage('Debe introducir una contraseña');
CanClose:=False;
end
else if (ModalResult=mrOK) and (PW1.Text <> PW2.Text) then
begin
ShowMessage('Verificación fallida. Por favor reintente');
CanClose:=False;
end;
end;
end.
Otros Links de Interés:
Cocina - Videos - Juegos Gratis - Postales cachondas - Cine - Programas Gratis - Letras de Canciones
Listas de todos los Tutoriales Gratis. 1998- 2007 - -
Los
tutoriales y cursos aquí reunidos son una recopilación de los mejores encontrados en
Internet.
El crédito y copyright de los mismos si lo hubiere corresponde al autor de cada
uno de ellos.
Si tu tutorial o curso está aquí, y deseas darlo de baja de esta recopilación o
quieres añadir el tuyo,
envíanos un mensaje desde
aquí