delphi.gif - 582,0 K
Curso de creación de componentes en Delphi

Unidad 9. Editores de Propiedades (II).

delphi.gif - 582,0 K
Volver al índice Por Luis Roche emailed.gif - 15503,0 K

Bverde.gif - .325 KIntroducción

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 :)

Bverde.gif - .325 KUn componente de prueba

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.

Bverde.gif - .325 KEl editor de propiedades TFicheroProperty

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;
Bverde.gif - .325 KEl editor de propiedades TAliasProperty

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;
Bverde.gif - .325 KEl editor de propiedades TPasswordProperty

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;
Bverde.gif - .325 KEl editor de propiedades TDateTimeProperty

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

Bverde.gif - .325 KConclusiones

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:

Bverde.gif - .325 KCódigo fuente de los editores de propiedades.
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.

Luis Roche revueltaroche@redestb.es
Ultima modificación 06.11.1997

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í

Publispain - Fun