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 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; |
// 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) 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; |
{: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 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');
... |
// 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');
...