Modificar propiedades de controles en ejecución utilizando RTTI
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; |
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; |
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'); ... |
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,…