Sobre el artículo del boletín…

junio 30, 2006 en Entrada Diario

Un pequeño comentario… Hace unos días, entregué a Jose Luis Freire el primero de los artículos con los que voy a intentar contribuir en el boletín. Me comentó que casi con seguridad saldría en el mes de Agosto. :-)
Este artículo y alguno mas (todavía no se si uno o dos mas porque el tema puede ser muy extenso) tratarán sobre ModelMaker, que es una herramienta case que basa la generación del código sobre los diseños UML creados. Ha existido desde las primeras versiones de Delphi pero quizás se dió un poco mas a conocer al incluirla Borland dentro de la versión 7 de Delphi. Yo al menos, la descubrí al empezar a trabajar sobre esta versión pero no fue hasta hace algunos meses cuando me decidí a trabajar mas intensamente con ella.
Ah… respecto al número de artículos que pueda incluir dentro de esta pequeña serie sobre ModelMaker, realmente no lo tengo claro. Este primero es una rápida introducción. Del resto la duda se centra principalmente en dejar fuera el tema del lenguaje UML sin que pierda claridad la serie, ya que UML, de por si, ya es materia para muchos artículos.
Espero que os guste.

Una etiqueta más util…

junio 16, 2006 en Delphi

No se si resultará util o no, pero el comentario al menos yo creo que sí. Así que me gustaría compartir esta reflexion con vosotros.

Veamos… El tema es que andaba estos días trabajando sobre un pequeño framework que me tiene bastante distraido de todo, y tras repetir en una de las fichas, la inserción de una etiqueta para cada campo que debía aparecer, y tener que modificar el captión de la etiqueta para que apareciera el display que iba a ver el usuario, se me ocurrió que me sería util que dicha etiqueta mostrase directamente el valor asignado al campo DisplayLabel vinculado al control de edición (no es exactamente así pero creo que me entendéis ya que la propiedad DisplayLabel pertenece a los campos persistentes vinculados). Hoy no ando demasiado fino explicandome :-D

En realidad, lo verdaderamente util sería que se pudiera arrastar los campos persistentes del dataset sobre el formulario y que se convirtieran por arte de magia en los controles de edición deseados. Esta operación no resulta util desgraciadamente porque tal y como esta implementada esta característica, por defecto generaría los controles de la pestaña de edición de bases de datos (TDbEdit, etc…) y no los que pudiera elegir el usuario (el programador podría querer utilizar componentes de terceros). ¿Tiene algun sentido arrastrar para crear y luego borrarlos porque no son los que necesitas? Ese tipo de cosas, si se analizan bien, son las que nacen de una buena inspiración pero que el espiritu humano abandona con la misma presteza.

Así, que se me ocurrio distraerme un rato modificando una etiqueta (el componente ancestral que llamamos como TLabel) :-) para que tuviera el comportamiento que deseaba. La etiqueta debía modificar el valor del campo Caption si el componente asignado a la propiedad FocusControl era un componente de edición de bases de datos. Para reconocerlo como tal, me podría bastar saber que disponía de la propiedades FieldName y DataSource, cosa que pensaba se podría saber mediante información de tipos (RTTI).

Pues vamos a ello.

Lo primero es crear el descendiente de la etiqueta y sobrescribir la propiedad FocusControl para darle el comportamiento deseado.

 TLabelAsociado = class(TLabel)
 private
   //metodos de escritura y lectura
   procedure SetFocusControl(Value: TWinControl);
   function GetFocusControl: TWinControl;
 { Private declarations }
 published
 { Published declarations }
   property FocusControl: TWinControl read
GetFocusControl write SetFocusControl;
 end;

function TLabelAsociado.GetFocusControl: TWinControl;
begin
   Result:= inherited FocusControl;
end;

procedure TLabelAsociado.SetFocusControl(Value: TWinControl);
begin
  inherited FocusControl := Value;
//aquí actuaremos…
end;

Será en el método de escritura en el que intentaremos obtener el valor de la etiqueta.
En la web de Neftali (la dirección esta en uno de los enlaces) hay un truco para obtener una lista de las propiedades en tiempo de ejecución. Lo que vamos a hacer es sobre ese truco, modificarlo un poco para que por un lado nos devuelva el valor de la propiedad FieldName (el procedimiento GetRTTIControlInfo)
y por otro lado la instacia del DataSource que apunta a la tabla de datos (la función GetRTTIDataSourceObject).

Esta es la únidad resultante por si quereis echarle un vistazo:

unit uLabelAsociado;


//*********************************************
// Componente TLabelAsociado
//
//*********************************************
// Fecha creación: 15/06/2006
// Autor         : Salvador Jover
// Mail          : salvador@sjover.com
// HTTP          : http://www.sjover.com/delphi
//
//*********************************************
//
// Objetivo : Modificar el caption de la etiqueta vinculandolo con el valor
//            de la propiedad DisplayLabel de los campos persistentes de las
//            tablas
//
// Reflexión: Es un ejercicio sobre el uso de RTTI para cuando uno se aburre…
//
// Ver      : Página de Neftali
//

Neftali (truco)

//

interface

uses
SysUtils, Classes, Controls, StdCtrls, Dialogs, TypInfo, DB;

type

 PObjetoInfo = ^TObjetoInfo;
 TObjetoInfo = record
   AInfo: PPropInfo;
   AObjeto: TObject;
 end;

 TLabelAsociado = class(TLabel)
 private
   //metodos de escritura y lectura
   procedure SetFocusControl(Value: TWinControl);
   function GetFocusControl: TWinControl;
 { Private declarations }
 published
 { Published declarations }
   property FocusControl: TWinControl read GetFocusControl write SetFocusControl;
 end;

procedure Register;

implementation

procedure Register;
begin
   RegisterComponents(‘Samples’, [TLabelAsociado]);
end;

{ TLabelAsociado }


// *********************************************
// *********************************************
// Metodo    : GetRTTIDataSourceObject
// Parametros: AControl: TObject
// Resultado : TDataSource
//
// Objetivo  : Obtener la instacia del datasource al que pertenece la propiedad
//
// *********************************************

function GetRTTIDataSourceObject(AControl: TObject): TDataSource;
var
  i: integer;
  props: PPropList;
  tData: PTypeData;
  FObject: TObject;
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;
  //Obtenemos memoria para la estructura que almacenará la propiedades
  GetMem(props, tData^.PropCount * SizeOf(Pointer));
  try
     //rellenamos la estructura con la información de clase
     GetPropInfos(AControl.ClassInfo, props);
     //recorremos la lista de propiedades
     for i := 0 to tData^.PropCount – 1 do begin
        //si la propiedad es de tipo clase (capaz de convertirse en un objeto)
        if Props^[i]^.PropType^^.Kindin [tkClass] then begin
          //vamos a apuntar hacia dicho objeto
           FObject:= GetObjectProp(AControl, Props^[i]);
           //solo consideraremos aquellos objetos referenciados
           if (FObject <> nil) and (FObject is TDataSource) then begin
              //hemos encontrado nuestro datasource
              Result:= (FObject as TDataSource);
              Exit; //abur…
           end
           else if FObject <> nil then begin
              //es una instancia pero no de la clase que buscamos pero…
              //¿estará dentro de dicha instancia el datasource buscado?
              //recursivamente lo sabermos

              Result:= GetRTTIDataSourceObject(FObject);
              //si tuviera exito nos vamos…
             if Result <> nil then Exit; //abur…
           end;
        end;
     end;
  finally
     FreeMem(props); //liberamos finalmente la memoria asignada dinámicamente
  end;
end;

// *********************************************
// *********************************************
// Metodo    : GetRTTIControlInfo
// Parametros: var AObjetoInfo: TObjetoInfo; AControl: TObject; AProperty: string
//
// Objetivo  : Devolver el par Objeto-Informacion_Extructur_propiedades
//             en la variable AObjetoInfo que nos permita obtener el valor
//             de la propiedad consultada (FieldName).
//
// Comentario: No solo nos hace falta la estructura de propiedades del objeto
//             sino también el mismo objeto puesto que de estar anidadas
//             no coincidiría con el original que es pasado como parámetro
//
// *********************************************

procedure GetRTTIControlInfo(var AObjetoInfo: TObjetoInfo; AControl: TObject; AProperty: string);
var
  i: integer;
  props: PPropList;
  tData: PTypeData;
begin
  //Inicializamos la estructura que va a contener el par de retorno Info-Objeto
  //la Precondicion AInfo = nil garantiza la finalización con el valor de estructura
  with AObjetoInfo do begin
     AInfo:= nil;
     AObjeto:= nil;
  end;
  // 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;
  //Obtenemos memoria para la estructura que almacenará la propiedades
  GetMem(props, tData^.PropCount * SizeOf(Pointer));
  try
     //rellenamos la estructura con la información de clase
     GetPropInfos(AControl.ClassInfo, props);
     //recorremos la lista de propiedades
     for i := 0 to tData^.PropCount – 1 do begin
        //si la propiedad es de tipo clase (capaz de convertirse en un objeto)
        if Props^[i]^.PropType^^.Kind in [tkClass] then begin
           GetRTTIControlInfo(AObjetoInfo, GetObjectProp(AControl, Props^[i]), AProperty);
          //si ha tenido exito
           if AObjetoInfo.AInfo <> nil then begin
              Exit; //nos vamos…
           end;
        end
        else
        if (Props^[i]^.Name = AProperty) then begin
           with AObjetoInfo do begin
              AInfo:= Props^[i];
              AObjeto:= AControl;
           end;
           Exit; //nos vamos… Encontrado par [Objeto/Info]
        end;
     end;
  finally
     FreeMem(props);
  end;
end;

// *********************************************
// *********************************************
// Metodo : GetFocusControl
// Parametros:
// Resultado : TWinControl
//
// Objetivo : Metodo de lectura de la propiedad FocusControl
//
// *********************************************

function TLabelAsociado.GetFocusControl: TWinControl;
begin
   Result:= inherited FocusControl;
end;

// *********************************************
// *********************************************
// Metodo : SetFocusControl
// Parametros: Value: TWinControl
// Resultado : Void
//
// Objetivo : Metodo de escritura de la propiedad FocusControl
//
// *********************************************

procedure TLabelAsociado.SetFocusControl(Value: TWinControl);
var
  pOInfo: PObjetoInfo;
  FDataSource: TDataSource;
  FFieldName: String;
begin
  //asignamos el valor al ascendente y que haga lo
que tenga que hacer

  inherited FocusControl := Value;
  //ahora empezamos nosotros…
  //solo actuaremos en tiempo de diseño
  if (Value = nil) or (ComponentState <> [csDesigning]) then Exit;
  //reservamos memoria a la estructura TObjetoInfo que vamos a apuntar
  GetMem(pOInfo, SizeOf(TObjetoInfo));
  try
     // Acceder a la info de la propiedad
     GetRTTIControlInfo(poInfo^, Value, ‘DataField’);
     //comprobamos que hemos tenido un resultado positivo y la estructura
     //contiene la propiedad deseada.

     if (pOInfo.AInfo <> nil) then begin
        //esta comprobación es redundante pero la dejo como segurida
        //puesto que pudiera darse el caso de existir un componente
        //con el mismo nombre de propiedad y distinto tipo…

        if (pOInfo.AInfo.PropType^^.Kind in [tkLString]) then begin
           // Obtenemos el valor
           FFieldName:=
          Format(‘%s’, [GetStrProp(pOInfo.AObjeto, pOInfo.AInfo)]);
           //Vamos a obtener el datasource
           FDataSource:= GetRTTIDataSourceObject(Value);
           //si ha tenido exito y se puede obtener el valor de la etiqueta
           if Assigned(FDataSource) and Assigned(FDataSource.DataSet)
                                     and ;(Trim(FFieldName) <> ”)then begin
             Caption:=
         FDataSource.DataSet.FieldByName(FFieldName).DisplayLabel;
              Exit; //hemos acabado
           end;
        end;
     end;

     Caption:= ”; //falló lo anterior y aquí vaciamos el valor del Caption
  finally
     FreeMem(pOInfo); //liberamos la memoria dinámica
  end;
end;

end.

Bueno… la he probado varias veces y aparentemente funciona.

El ejercicio me ha valido para reflexionar un tanto sobre la RTTI y quizás sobre otros aspectos que aparecen al hilo de la reflexion:
* ¿Por qué acabé finalmente resignandome a utilizarlo en tiempo de diseño?
* En teoría debería haber utilizado una función para devolver el resultado en lugar de un procedimiento con una variable por referencia en GetRTTIControlInfo…
* ¿Funcionará en cualquier caso?

Y quizás la última reflexión que se me ocurre, es que ante un problema cualquiera, cada programador encontrará soluciones quizás no validas globalmente, pero si útiles. Pensad por ejemplo si en lugar de querer crear un descendiente de TLabel lo hubiera hecho de un TDbEdit con el fin de acompañar en tiempo de diseño y ejecución una etiqueta. Bastaría crear la etiqueta en el contstructor del TDbEdit y situarla sobre el mismo parent asociado al componente, guardando la distancia deseada del TdbEdit. En ese caso, la idea base sería sobrescribir la propiedad DataField en el TdbEdit y modificar el captión de la etiqueta de acuerdo al nuevo valor. En ese caso, no nos haria falta jugar con la RTTI.

Hubiera sido otra posible solución, sin embargo, la experiencia del día a día me dice que para eso, tendría que tener preparados en la paleta de componentes, una bateria adicional de descendientes de los mas utilizados, que a la larga quedaría sin uso y arrumbada. Sin embargo, para un programador que siempre usara los mismos controles de edición de datos, podría ser una buena dinámica de trabajo.

Os dejo que saqueis vuestras propias conclusiones… (yo vuelvo al trabajo y a mi FrameWork dichoso)

Que tengáis un buen día.

Descargar el codigo fuente: uLabelAsociado

Cambio de tema…

junio 9, 2006 en Entrada Diario

El sistema de WordPress que mantiene estas páginas permite cambiar las presentación de cada blog tan solo seleccionando la opción correspondiente. Así que tras pensarlo y valorarlo durante unos días, he creido conveniente cambiar el “Thema”. Me daba “penita” porque había pasado algunas horas incrustando la imagen del templete que representaba a Delphi sobre el motivo original de la hoja. Y los colores eran más cálidos que este gélido blanco.

No obstante, el blog tenía desactivadas las opciones de búsqueda y pensaba finalmente, que el tenerlas activas justificaba el cambio. Así que la elección del fondo blanco responde a facilitar la lectura, si es que alguien lo encuentra finalmente de interes… :-)

Hoy no tengo demasiado que decir. Ha sido un día tranquilo, que he dedicado a leer y profundizar sobre algunos temas. La escritura del artículo me ha abierto algunas inquietudes que estoy intentando satisfacer en los ratos que dispongo algo de tiempo. He dedicado parte de la mañana a esa tarea en las horas en las que el tibio sol se colaba por la ventana de mi escritorio, y algo mas tarde, cuando ya estaba cercana la hora de la comida, he leido un par de capítulos del último libro que compré. Y poco más.

Un día ciertamente tranquilo.

Dime algo sobre Firebird…

junio 8, 2006 en Artículos, Delphi, Enlace interesante, Entrada Diario, Firebird

En los correos que hoy he leido, me he encontrado con una buena referencia a Firebird. El enlace aparecía en el foro de Alfonso Ricaño, que como ya sabéis es uno de las mejores fuentes de información en castellano sobre Firebird/Interbase.
El enlace apuntaba a:
http://www.destructor.de/index.htm [en inglés]

Me han parecido interesantes las referencias a Firebird puesto que son bastante concisas y estoy completamente seguro de que la persona que empieza a dar los primeros pasos en este tema, lo va a agradecer. Tambien vais encontrar de forma resumida las diferencias existentes entre las distintas versiones de firebird.

En ocasiones, tener la cantidad de información precisa es importante. Es preferible poco y claro a mucho y confuso… Aunque siempre hay quien dice que: -Ande o no ande, burro grande. :-)
Yo prefiero lo primero…

Por cierto… muchos compañeros que empiezan a dar sus primeros pasos en Firebird preguntan a menudo cual puede ser una buena herramienta para trabajar con este bbdd. Hay muchas y muy buenas. Yo voy a referir la que yo uso en mis desarrollos, ya que me parece excepcional por diferentes razones. Hablo de IbExpert.

Hasta el momento existe una versión personal, libre, que podéis valorar vosotros mismos. Lógicamente, la versión profesional trae muchas carácterísticas que serán sin duda valoradas. Una de las aspectos que me parecieron más interesantes por poner un ejemplo, es la posibilidad de depurar los procedimientos almacenados pero existen muchos mas relacionados con la creación de permisos, o la generación de sentencias sql complejas y su validación.