Esta entrada nace de una necesidad, la que he intentado explicar en el título, pero que tal vez, por la falta de espacio ha quedado «parca» y poco clara. Se trata de una estructura de clases almacenada en memoria y que utilizo en una de mis aplicaciones. Llegado a este punto tengo la necesidad de «respaldar» esta estructura en disco, para posteriormente, desde este mismo programa o desde otro, poder recuperarla. Lo que comúnmente podemos llamar como un backup/restore.
Se trata de una estructura jerárquica de clases, en la que unas incluyen a otras y en la que además podemos encontrar listas de elementos. En una clase podemos encontrar propiedades de tipos simples (string, cadena), propiedades con objetos de otras clases y listas que almacenan objetos de otras clases.
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Ya hace días que desde Embarcadero se publicó el anuncio y la información sobre el “Programa de Certificación Delphi”. Dos niveles, el Developer y el Master Developer.
Dentro de las Guías de estudio (los dos PDF’s que hay más arriba), además de la información general y de temario, se pueden encontrar unos test de ejemplo para hacerse una idea del nivel de cada una de las certificaciones.
En Delphi About, hace unos días se publicaba un interesante artículo sobre la impresión de documentos desde Delphi; Impresión de cualquier tipo de fichero que pueda existir en el sistema. En un tema/pregunta habitual en los foros (y una necesidad en nuestros programas).
Hace unos días Andreano Lanusse realizó un Webminar gratuito titulado “Aprenda lo que usted puede hacer con el conjunto de herramientas adicionales en RAD Studio XE”. Muy interesante e ilustrativo. El video completo de la presentación lo podéis ver y descargar desde aquí.
Recomendable la visita esta semana a Delphi Heaven para revisar la entrada sobre RTTI; “Object Cloning using the hight level RTTI”. Tanto la explicación como los comentarios son muy interesantes. Todo ello puede ayudar a entender un poco más la potencia y las bondades de la RTTI (esa gran desconocida… ;-D )
Por ultimo, un enlace de estos que llamamos “practicos”; Al menos para mí, porque es una cosa que de forma periódica y recurrente necesito y nunca tengo “a mano”. Se trata de las directivas de compilación para las diferentes versiones de Delphi. Las ha publicado Jordi Coll en su blog y os las copio aquí:
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Pues ha llovido mucho desde la primera parte de este «articulillo»; Por lo que he visto en los fuentes del ejemplo, lo empecé hace aproximadamente hace 2 años, así que eso es lo que lleva de retardo… ;-)
En ese primer artículo se daba una visión general de lo que podía ser un sistema de PlugIns. Unas ideas generales y algo de código para empezar. Quedaba en el tintero profundizar un poco más en el tema y ver un ejemplo un poco más «práctico» que pudiera servir en una aplicación real. Eso es lo que he intentado tratar en esta segunda parte, centrandome en un sistema de «PlugIns Homogéneos«, con carga bajo petición (por parte del usuario).
TIPOS DE PLUGINS
Como ya vimos en la primera entrega, podemos dividir los plugIns en dos tipos según las tareas que desempeñan en una aplicación. Así podemos hablar de plugIns o grupos de ellos y catalogarlos como homogéneos si la tarea que realizan es similar o catalogarlos como heterogéneos (no-agrupados) si las tareas que desarrollan son independientes y no “agrupables” según su funcionalidad.
Grupos homogéneos de PlugIns
PlugIns heterogéneos
Esta división no sólo es conceptual en función de las características y desempeños de cada uno, sino que afecta directamente a la estructura con que se diseñarán estos elementos. Así, los PlugIns que pertenezcan a un grupo homogéneo tendrán estructura similar y un punto de entrada común (formulario base o procedimiento de ejecución). Mientras que los heterogéneos posiblemente no tengan una estructura común y la forma se ejecutarlos sea más “tosca” y menos “integrada” que los anteriores.
PLUGINS HOMOGENEOS
En este artículo vamos a tratar más profundamente esta variante de plugIns. Como ya hemos comentado se trata de plugIns con una estructura similar, aunque con variaciones en su funcionalidad. Tal vez con un ejemplo se vea más claro.
Tomemos como grupo homogéneo de PlugIns; Los efectos aplicables a una imagen dentro de un programa de diseño. A partir de una imagen podemos desarrollar plugIns que efectúen un determinado “cambio” de forma que la imagen resultante sea diferente a la original. La estructura y los parámetros de todos ellos parece claro que serán similares o idénticos. Todos ellos toman una imagen inicial y a partir de unos parámetros la modifican, para devolver una imagen de salida.
ESTRUCTURA FÍSICA
Para trabajar con esta estructura de plugins, utilizaremos un sistema de carga dinámica. Los plugins de programarán utilizando packages (BPL) con una estructura común y dependiendo de un package principal que contiene la Clase Base. Todos los plugins derivarán (heredarán) de una clase base que estará programada en el package principal.
Al cargar la aplicación se carga (puesto que está linkado de forma estática -utilizando la clausula USES-) también el package correspondiente a la clase Base. Esto da acceso a todos los métodos que estén definidos en la clase base (y en los derivados) desde el programa principal.
El resto de packages se cargan de forma dinámica y todos deben derivar (sus clases) de la Clase Base programada en el package Base.
PROTOTIPO
El prototipo que vamos a realizar para ilustrar el artículo simula un programa para realizar gráficos y diagramas simples. El programa utilizará un sistema de plugIns para añadir bibliotecas de objetos que puedan añadirse a los gráficos. Cada pluging (BPL) añade una nueva categoría de elementos y cada categoría implementa uno o varios objetos.
Todos los objetos que implementa una categoría derivan de una Clase Base (TShapeExBase) y esta clase base se implementa en un package que está linkado estáticamente a la aplicación principal (se carga siempre al arrancar la aplicación) y es obligatorio que exista, de otra forma la aplicación fallaría al ejecutarse.
En la imagen que se ve ala derecha, vemos la ventana correspondiente al Plugin de «Arrows»; Aquí implementa la clase TShapeExArrow (que deriva de TShapeExBase) y en esta clase se han programado los objetos que se ven en la imagen.
En nuestro ejemplo para este artículo se cargan los plugIns bajo petición. Es decir, en una primera pasada la aplicación revisa la existencia de PlugIns y detecta todos los ficheros presentes. Muestra una ventana con los plugns disponibles y la descripción de cada uno de ellos y a medida que el usuario los selecciona se cargan de forma dinámica. Imagen de la derecha.
El código de la carga es el siguiente:
...// ComprobaciónifnotFileExists(AName)thenbegin
_mens(Format('No se ha podido cargar el package <%s>;'+'No existe en disco.',[AName]));
Exit;end;// Cargar
hndl :=LoadPackage(AName);
desc :=GetPackageDescription(PChar(AName));
Result := hndl;// Acceder a la clase del menu
pName :=ChangeFileExt(ExtractFileName(AName),'');
b := ExClassList.Find(pName, i);// Encontrada?if(b)thenbegin
AClass := TPersistentClass(ExClassList.Objects[i]);end;
...
// Comprobación
if not FileExists(AName) then begin
_mens(Format('No se ha podido cargar el package <%s>;' +
'No existe en disco.',[AName]));
Exit;
end;
// Cargar
hndl := LoadPackage(AName);
desc := GetPackageDescription(PChar(AName));
Result := hndl;
// Acceder a la clase del menu
pName := ChangeFileExt(ExtractFileName(AName), '');
b := ExClassList.Find(pName, i);
// Encontrada?
if (b) then begin
AClass := TPersistentClass(ExClassList.Objects[i]);
end;
CLASE BASE (TShapeExBase)
La clase base para nuestro sistema de plugins se llama TShapeExBase. Esta clase sirve como punto de partida para todas las demás. Además de contener los métodos comunes a todos los plugins nos permitirá acceder desde la aplicación principal a todas las funciones de los plugins. Para ello los métodos importantes estarán definidos en esta clase y luego sobreescritos (override) en las clases derivadas.
{: Clase base lapa las clases implementadas en los plugins.}
TShapeExBase =class(TShape)private
FShapeEx:string;// Marca el tipo de Shapeprocedure SetShapeEx(const Value:string);virtual;protected
W, H, S:Integer;
X, Y:Integer;
XW, YH:Integer;
W2, H2, W3, H3, H4, W4, H8, W8, W16, H16:Integer;// Método de pintadoprocedure Paint;override;procedure CalculateData();// PROCEDIMIENTOS DE INFORMACION//············································// Autor del packagefunction Autor():string;virtual;abstract;// Versión del Packagefunction Version():string;virtual;abstract;// Fecha de creaciónfunction FechaCreacion():TDate;virtual;abstract;public// constructor de la claseconstructor Create(AOwner: TComponent);override;// destructor de la clasedestructor Destroy;override;published// Tipo de Shapeproperty ShapeEx:stringread FShapeEx write SetShapeEx;end;
{: Clase base lapa las clases implementadas en los plugins.}
TShapeExBase = class(TShape)
private
FShapeEx: string;
// Marca el tipo de Shape
procedure SetShapeEx(const Value: string); virtual;
protected
W, H, S: Integer;
X, Y:Integer;
XW, YH:Integer;
W2, H2, W3, H3, H4, W4, H8, W8, W16, H16:Integer;
// Método de pintado
procedure Paint; override;
procedure CalculateData();
// PROCEDIMIENTOS DE INFORMACION
//············································
// Autor del package
function Autor():string; virtual; abstract;
// Versión del Package
function Version():string; virtual; abstract;
// Fecha de creación
function FechaCreacion():TDate; virtual; abstract;
public
// constructor de la clase
constructor Create(AOwner: TComponent); override;
// destructor de la clase
destructor Destroy; override;
published
// Tipo de Shape
property ShapeEx: string read FShapeEx write SetShapeEx;
end;
En nuestro caso es una clase sencilla. La función implementa el dibujo de componentes derivados de un TShape en pantalla.
La propiedad ShapeEx es la más importante, e indica el tipo (identificador) de la figura. Equivalente a lo que en los TShape son los valores stRectangle, stEllipse, stSquare,…
En nuestra clase no puede ser un elemento tipificado como lo es en TShape, puesto que los nuevos plugins irán añadiendo elementos a esta propiedad que a priori no conocemos.
Se añaden también procedimientos de información acerca del plugin como pueden ser el Autor, la fecha de creación o la versión.
El método Paint, que para la clase base está vacío, en las clases derivadas será donde se implementen las instrucciones de pintado para cada uno de los elementos.
Finalmente la clase Base implementa el procedimiento CalculateData y al utiliza algunas variables en la parte protected, que precalculan datos y los ponen a disposición de las clases derivadas (protected), para facilitar la implementación del método Paint y dar acceso a medidas ya precalculadas.
En la clase Base además se definen dos Listas (TStringList) que nos servirán de apoyo a la hora de acceder a los diferentes objetos de los plugIns; Tanto para las clases, como para los Shapes definidos en cada clase.
//: Lista de clases registradas en los packages dinámicos
ExClassList:TStringList;//: Lista de objetos registrados en una clase (tipos de Shapes)
ExShapeList:TStringList;
//: Lista de clases registradas en los packages dinámicos
ExClassList:TStringList;
//: Lista de objetos registrados en una clase (tipos de Shapes)
ExShapeList:TStringList;
En la primera añadiremos la referencia a la Clase y el String correspondiente al nombre del package y en la segunda, para cada Shape implementado en la Clase, su valor de la propiedad ShapeEx (comentada anteriormente) y el apuntador a su clase.
De esta forma, por ejemplo, el PlugIn que implementa la clase TshapeExArrow que corresponde a la imagen que se ve más arriba, añadirá en las lista los siguientes valores:
// Registrar los tipos
ExShapeList.AddObject('stArrorRight',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW',Pointer(TShapeExArrow));...// registrar la clase
ExClassList.AddObject('PlugArrows',Pointer(TShapeExArrow));
// Registrar los tipos
ExShapeList.AddObject('stArrorRight', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW', Pointer(TShapeExArrow));
...
// registrar la clase
ExClassList.AddObject('PlugArrows', Pointer(TShapeExArrow));
En las líneas anteriores podemos ver que el plugIn PlugArrow (1) tiene implementada la clase TShapeExArrow (1), y que dentro de esta clase hay 6 objetos diferentes de tipo ShapeEx; Cuyos identificadores son: stArrorRight, stArrorRight, stArrorRightM, stArrorLeft, stArrorUp y stArrorDown.
CLASES DERIVADAS
Tal y como está diseñada la estructura, las clases derivadas de la clase Base (TShapeExBase) deben redefinir el método Paint para definir cómo se define cada uno de los objetos de esa clase.
SISTEMA DE CARGA/DESCARGA
El sistema de carga es simple y lo único que hace de especial en este caso es comprobar primero si el package ya ha sido cargado, y si no es así llama a la función CargarPackage del formulario principal, utilizando el nombre del fichero.
Podemos ver por pasos y comentar qué hace esta función:
// Cargar
hndl :=LoadPackage(AName);
desc :=GetPackageDescription(PChar(AName));
Result := hndl;
En primer lugar (una vez hemos comprobado que el fichero existe) cargamos el package a partir de su nombre. Una vez cargado obtenemos la Descripción. Para ello se llama a la función GetPackageDescription que se encuentra en la Unidad SysUtils.pas y que develve el valor almacenado en el DPK junto a la directiva {$DESCRIPTION} o {$D} que permite almacenar hasta 255 caracteres.
Todos los packages cuentan con una sección de INITIALIZATION donde añaden a las lista de clases (ExClassList) y a la lista de Shapes (ExShapeList) los elementos que ese package implementa. Estas dos clases son importantes puesto que nos facilitan mucho el trabajo a la hora de realizar todo tipo de operaciones con los elementos de cada packages. Además se registra la clase utilizando el método RegisterClass de Delphi. Por ejemplo, el package de “Arrows” contiene esta sección de INITIALIZATION:
//===================================================================//// I N I T I A L I Z A T I O N////===================================================================initialization// Registrar la clase del formRegisterClass(TShapeExArrow);// Registrar los tipos
ExShapeList.AddObject('stArrorRight',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightM',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorLeft',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorUp',Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorDown',Pointer(TShapeExArrow));// registrar la clase
ExClassList.AddObject('PlugArrows',Pointer(TShapeExArrow));//===================================================================
//===================================================================
//
// I N I T I A L I Z A T I O N
//
//===================================================================
initialization
// Registrar la clase del form
RegisterClass(TShapeExArrow);
// Registrar los tipos
ExShapeList.AddObject('stArrorRight', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightW', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorRightM', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorLeft', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorUp', Pointer(TShapeExArrow));
ExShapeList.AddObject('stArrorDown', Pointer(TShapeExArrow));
// registrar la clase
ExClassList.AddObject('PlugArrows', Pointer(TShapeExArrow));
//===================================================================
Lo siguiente que vamos necesitamos, una vez que tenemos cargado el package, es crear la clase que se implementa en el package; Una vez hecho esto ya tendremos total acceso a los métodos que necesitemos y realmente ya habremos conseguido nuestro objetivo.
Para crear la clase utilizamos la lista de clases (ExClassList) que hemos comentado en el párrafo anterior y que hemos rellenado en la sección de inicialización; Otra opción también viable es utilizar GetClass de Delphi mediante RTTI junto con el nombre de la clase registrada (TShapeExArrow). También funcionaría, aunque en este caso, por comodidad, hemos utilizado estas listas auxiliares.
// Crear la clase
b := ExClassList.Find(pName, i);// encontrada?if(b)thenbegin
AClass := TPersistentClass(ExClassList.Objects[i]);// OTRA OPCIÓN:
BClass :=GetClass('TShapeExArrow');end;
// Crear la clase
b := ExClassList.Find(pName, i);
// encontrada?
if (b) then begin
AClass := TPersistentClass(ExClassList.Objects[i]);
// OTRA OPCIÓN:
BClass := GetClass('TShapeExArrow');
end;
Para finalizar y después de haber realizado unas comprobaciones, llamamos al método CargarCategoria, que crea de forma dinámica la ventana asociada a esa categoría (con la descripción) y también crea el elemento individual asociada a cada Shape implementado en esa clase.
En este punto ya hemos hecho uso de todo lo implementado en ese package, puesto que ya hemos creado un objeto de todos los implementados.
// Cargar los objetos de ese plugIn
CargarCategoria(AClass, desc);
En este ejemplo, no descargamos los packages, puesto que los necesitamos para seguir trabajando con los objetos que tenemos en pantalla, lo que hacemos realmente es ocultar la ventana. Si la operación que desempeña el package no necesita que posteriormente esté cargado, bastaría con descargarlo utilizando UnloadPackage.
Hasta aquí las descripción de todo el proceso. Junto con el artículo os adjunto el ejemplo completo y bastante comentado. Es sencillo, pero muestra a la perfección el manejo práctico de este tipo de ficheros.
Espero que haya quedado claro y si hay comentarios o sugerencias, ya sabéis. ¡¡Disparad!! ;-D
Imagen del programa de ejemplo.
El código del ejemplo se puede descargar desde aquí y los binarios (EXE + BPL’s) desde aquí.
Tal como me comenta Salvador, en el proyecto no se incluyen las dos BPL’s de Dephi (de la VCL) que ne necesitan para ejecutar el proyecto. Si no tenéis Delphi 6 instalado, las necesitaréis para ejecutar. Os coloco los links, con el proyecto (binarios incluyendo las BPLs) y un fichero sólo con los dos ficheros (VCL60.BPL y RTL60.BPL).
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
A veces durante la ejecución de código necesitamos modificar una determinada propiedad de diferentes tipos de controles. Para la explicación podemos pensar por ejemplo en como desactivar (Enabled=False) todos los controles de un formulario; Una primera opción podría ser un código como éste:
// recorrer los controlesfor i :=0to(Self.ComponentCount-1)dobegin// Es un Editif(Components[i]is TEdit)then
TEdit(Components[i]).Enabled:=False;// Es un LAbelif(Components[i]is TLabel)then
TLabel(Components[i]).Enabled:=False;// Es un ListBoxif(Components[i]is TListBox)then
TListBox(Components[i]).Enabled:=False;...end;
// recorrer los controles
for i := 0 to (Self.ComponentCount - 1) do begin
// Es un Edit
if (Components[i] is TEdit) then
TEdit(Components[i]).Enabled := False;
// Es un LAbel
if (Components[i] is TLabel) then
TLabel(Components[i]).Enabled := False;
// Es un ListBox
if (Components[i] is TListBox) then
TListBox(Components[i]).Enabled := False;
...
end;
Una implementación como ésta tiene muchos problemas e inconvenientes; Los más claros podrían ser:* Es poco «ortodoxa», por decirlo así;
Es un código repetitivo y nada eficiente.
Es por definición incompleta, ya que el número de componentes que podemos tener en un form es inmenso y de muchas clases (yo sólo las básicas que trae Delphi).
Es poco flexible.
…
Utilizando RTTI podemos de forma relativamente sencilla modificar todos los componentes utilizando una única instrucción.
Podemos utilizar un procedimiento como éste:
{:Asigna valor a una propiedad a través del Nombre (RTTI). }function SetPropAsString(AObj:TObject;const PropName, Value:String):Boolean;var
PInfo: PPropInfo;begin// Intentamos acceder (con un puntero) a la info. de la propiedad
PInfo := GetPropInfo(AObj.ClassInfo, PropName);
Result := PInfo <>nil;// Se ha obtenido la información...if(Result)thenbegin// Se ha encontrado la propiedad con éste nombre; Chequear el tipo...if(PInfo^.Proptype^.Kind= tkString)or(PInfo^.Proptype^.Kind= tkLString)thenbegin// Asignar el valor de tipo String
SetStrProp(AObj, PInfo, Value);endelseif(PInfo^.Proptype^.Kind= tkInteger)thenbegin// Asignar el valor...if(PInfo^.PropType^.Name='TColor')thenbegin
SetOrdProp(AObj, PInfo, StringToColor(Value));endelseif(PInfo^.PropType^.Name='TCursor')thenbegin
SetOrdProp(AObj, PInfo, StringToCursor(Value));endelsebegin
SetOrdProp(AObj, PInfo,StrToInt(Value));end;endelseif(PInfo^.Proptype^.Kind= tkEnumeration)thenbegin// Bloque de protecciontryif(PInfo^.PropType^=TypeInfo(System.Boolean))thenbegin
SetOrdProp(AObj, PInfo,StrToInt(Value));endelsebegin
SetOrdProp(AObj, PInfo,StrToInt(Value));end;exceptraise;end;endelsebegin
Result :=False;end;endelsebegin// No se ha encontrado la propiedad con ese nombre
Result :=False;end;end;
{:Asigna valor a una propiedad a través del Nombre (RTTI). }
function SetPropAsString(AObj: TObject; const PropName, Value: String):Boolean;
var
PInfo: PPropInfo;
begin
// Intentamos acceder (con un puntero) a la info. de la propiedad
PInfo := GetPropInfo(AObj.ClassInfo, PropName);
Result := PInfo <> nil;
// Se ha obtenido la información...
if (Result) then begin
// Se ha encontrado la propiedad con éste nombre; Chequear el tipo...
if (PInfo^.Proptype^.Kind = tkString) or
(PInfo^.Proptype^.Kind = tkLString) then begin
// Asignar el valor de tipo String
SetStrProp(AObj, PInfo, Value);
end
else if (PInfo^.Proptype^.Kind = tkInteger) then begin
// Asignar el valor...
if (PInfo^.PropType^.Name = 'TColor') then begin
SetOrdProp(AObj, PInfo, StringToColor(Value));
end
else if (PInfo^.PropType^.Name = 'TCursor') then begin
SetOrdProp(AObj, PInfo, StringToCursor(Value));
end
else begin
SetOrdProp(AObj, PInfo, StrToInt(Value));
end;
end
else if (PInfo^.Proptype^.Kind = tkEnumeration) then begin
// Bloque de proteccion
try
if (PInfo^.PropType^ = TypeInfo(System.Boolean)) then begin
SetOrdProp(AObj, PInfo, StrToInt(Value));
end
else begin
SetOrdProp(AObj, PInfo, StrToInt(Value));
end;
except
raise;
end;
end
else begin
Result := False;
end;
end
else begin
// No se ha encontrado la propiedad con ese nombre
Result := False;
end;
end;
Su utilización es muy sencilla, y en el ejemplo anterior el código utilizado pasaría a ser similar a éste:
NOTA: Añadir la unit TypInfo al USES.
NOTA2: (Gracias Arsenio por comentarmelo) Se deben añadir al uses también las units Graphics y Controls ya que son usados por StringToColor y StringToCursor respectivamente.
// recorrer los controlesfor i :=0to(Self.ComponentCount-1)dobegin
SetPropAsString(Components[i],'Enabled','0'{FALSE});end;// Otros ejemplos
SetPropAsString(Components[i],'Left','10');
SetPropAsString(Components[i],'Color','clRed');...
// recorrer los controles
for i := 0 to (Self.ComponentCount - 1) do begin
SetPropAsString(Components[i], 'Enabled', '0'{FALSE});
end;
// Otros ejemplos
SetPropAsString(Components[i], 'Left', '10');
SetPropAsString(Components[i], 'Color', 'clRed');
...
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Éste truco aparece debido a la necesidad de obtener vía RTTI la siguiente información: Lista de todas las propiedades de un componente.
Valor de una propiedad de un componente sin utilizar el método siguiente (*).
Una primera aproximación para obtener la propiedad (Left, por ejemplo) de un componente del que a priori no conocemos el tipo, es utilizar una estructura IF o CASE (método 1) similar a la siguiente:
1
2
3
4
5
6
7
8
// Metodo 1 (*)// Segun el tipo de componente...if(compis TEdit)then
Result := TEdit(comp).Left;elseif(compis TLabel)then
Result := TLabel(comp).Left;elseif(compis TButton)then
Result := TButton(comp).Left;
// Metodo 1 (*)
// Segun el tipo de componente...
if (comp is TEdit) then
Result := TEdit(comp).Left;
else if (comp is TLabel) then
Result := TLabel(comp).Left;
else if (comp is TButton) then
Result := TButton(comp).Left;
Está claro que éste método es poco eficiente, poco flexible (siempre podemos dejarnos algún tipo de componente) y poco ortodoxo.
¿Cómo debe hacerlo Delphi para mostrar las propiedades de un componente en el inspector de objetos (por ejemplo)?
La respuesta es RTTI. Se puede acceder a las propiedades de un componente (a partir de la clase) y del nombre de la propiedad, utilizando la siguiente función (he añadido un parámetro nuevo para obtener en la misma llamada la lista de todas las propiedades -TStrings-).
{:Obtener la información de una propiedad a partir de la clase y el nombre;
Ademas devuelve la lista de todas las porpiedades de ese control.}function GetRTTIControlInfo(AControl: TPersistent;
propList:TStrings; AProperty:string): PPropInfo;var
i:integer;
props: PPropList;
tData: PTypeData;begin// Inicial
Result :=nil;// No asignado el control ==> Salimosif(AControl =nil)or(AControl.ClassInfo=nil)thenbegin
Exit;end;// Obtener la información
tData := GetTypeData(AControl.ClassInfo);// Tipo desconocido o sin propiedades ==> Salimosif(tData =nil)or(tData^.PropCount=0)then
Exit;GetMem(props, tData^.PropCount*SizeOf(Pointer));try
GetPropInfos(AControl.ClassInfo, props);for i :=0to tData^.PropCount-1dobegin
propList.Add(Props^[i]^.Name);with Props^[i]^dobeginif(Name = AProperty)thenbegin
result := Props^[i];end;end;end;finallyFreeMem(props);end;end;
{:Obtener la información de una propiedad a partir de la clase y el nombre;
Ademas devuelve la lista de todas las porpiedades de ese control.}
function GetRTTIControlInfo(AControl: TPersistent;
propList:TStrings; AProperty: string): PPropInfo;
var
i: integer;
props: PPropList;
tData: PTypeData;
begin
// Inicial
Result := nil;
// No asignado el control ==> Salimos
if (AControl = nil) or (AControl.ClassInfo = nil) then begin
Exit;
end;
// Obtener la información
tData := GetTypeData(AControl.ClassInfo);
// Tipo desconocido o sin propiedades ==> Salimos
if (tData = nil) or (tData^.PropCount = 0) then
Exit;
GetMem(props, tData^.PropCount * SizeOf(Pointer));
try
GetPropInfos(AControl.ClassInfo, props);
for i := 0 to tData^.PropCount - 1 do begin
propList.Add(Props^[i]^.Name);
with Props^[i]^ do begin
if (Name = AProperty) then begin
result := Props^[i];
end;
end;
end;
finally
FreeMem(props);
end;
end;
Un ejemplo de utilización podría ser el siguiente:
(basta con un formulario que tenga un TButton y un TMemo)
procedure TForm1.Button1Click(Sender:TObject);var
pInfo: PPropInfo;
propList:TStrings;
PStr:String;begin// Crear la lista
propList := TStringList.Create();// protección para liberartry// Acceder a la info de la propiedad
pInfo := GetRTTIControlInfo(Button1, propList,'Left');// La prop. encontrada es de tipo integer (debería ser, ya que es la// prop. Left)if(pInfo.PropType^^.Kindin[tkInteger])thenbegin// Obtener
PStr:='Left'+' = '+Format('%d',[GetOrdProp(Button1, pInfo)]);// Mostrar
MessageDlg(PStr, mtInformation,[mbOK],0);end;// Rellenar la lista de propiedades
Memo1.Lines.Assign(propList);finallyFreeAndNil(propList);end;end;
procedure TForm1.Button1Click(Sender: TObject);
var
pInfo: PPropInfo;
propList:TStrings;
PStr:String;
begin
// Crear la lista
propList := TStringList.Create();
// protección para liberar
try
// Acceder a la info de la propiedad
pInfo := GetRTTIControlInfo(Button1, propList, 'Left');
// La prop. encontrada es de tipo integer (debería ser, ya que es la
// prop. Left)
if (pInfo.PropType^^.Kind in [tkInteger]) then begin
// Obtener
PStr:= 'Left' + ' = ' + Format('%d', [GetOrdProp(Button1, pInfo)]);
// Mostrar
MessageDlg(PStr, mtInformation, [mbOK], 0);
end;
// Rellenar la lista de propiedades
Memo1.Lines.Assign(propList);
finally
FreeAndNil(propList);
end;
end;
Embarcadero MVP.
Analista y Programador de Sistemas Informáticos.
Estudios de Informática (Ingeniería Técnica Superior) en la UPC (Universidad Politécnica de Barcelona).
Llevo utilizando Delphi desde su versión 3. Especialista en diseño de componentes, Bases de Datos, Frameworks de Persistencia, Integración Continua, Desarrollo móvil,…
Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Si continúas usando este sitio, asumiremos que estás de acuerdo con ello.Aceptar