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,…
Yo se que este post tiene años… pero hoy lo usé… y te agradezco… por este post y por todo el blog que me ha ayudado mucho. Tengo en uso varios componentes tuyos y la verdad es que me han servido muchísimo para aprender.
Eso que hace 10 años que programo en Delphi… uno nunca deja de aprender nuevas cosas no?
Lo único que agregaría, para perfeccionarlo es que hay que agregar al uses: Graphics y Controls ya que son usados por StringToColor y StringToCursor respectivamente.
De nuevo, muchas gracias.
@Arsenio
Gracias Arsenio.
Corregido.