Crear Componentes Delphi
Crear Componentes Delphi
Crear Componentes Delphi
Conocimientos previos.
Pero este no es un curso de programacin en Delphi, ni un curso de programacin orientada a objetos. Se supone que el lector tiene ya un bagaje ms o menos amplio de estos temas y conceptos tales como herencia, descendencia, etc. no le resultan desconocidos. Como requisito previo a la creacin de componentes resulta altamente recomendable tener claros los siguientes conceptos:
Qu es la programacin orientada a objetos y sus aspectos fundamentales (constructores, destructores, clases, herencia, sobrecarga...) Dominio del entorno integrado de desarrollo de Delphi. Manejo con soltura de los distintos componentes estandard de Delphi. Utilizacin del ObjectBrowser para determinar relaciones entre objetos.
Si necesitas alguna aclaracin sobre estos temas, puedes encontrar toda la informacin necesaria en los propios manuales de Delphi, as como en la ayuda en lnea.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI En principio la periocidad del curso ser semanal o quincenal, pero todo depender de la respuesta que encuentre de vosotros, los usuarios del curso as como de mi disponibilidad a lo largo del tiempo (ya sabis, exmenes y esas cosas).
Propiedades Las propiedades proporcionan al usuario del componente un fcil acceso al mismo. Al mismo tiempo, permite al programador del componente "esconder" la estructura de datos subyacente. Entre las ventajas de utilizar propiedades para acceder al componente se pueden citar:
o
Las propiedades estn disponibles en tiempo de diseo. De este modo el usuario del componente puede inicializar los valores de las propiedades sin necesidad de escribir una sla lnea de cdigo. Las propiedades permiten la validacin de los datos al mismo tiempo de ser introducidas. As se pueden prevenir errores causados por valores invlidos. Nos aseguran que desde el primer momento nuestras propiedades tendrn un valor vlido, evitando el error comn de hacer referencia a una variable que no ha sido convenientemente inicializada.
Eventos Los eventos son las conexiones existentes entre un determinado suceso y el cdigo escrito por el programador de componentes. Asi por ejemplo, ante el suceso clic del ratn se podra mostrar un mensaje en pantalla. Al cdigo que se ejecuta cuando se produce un determinado evento se le denomina manejador de eventos (event handler) y normalmente es el propio usuario del componente quin lo escribe. Los eventos m comnes ya forman parte de los propios componentes de Delphi (acciones del ratn, pulsaciones de teclado...), pero es tambin posible definir nuevos eventos.
Mtodos Los mtodos son los procedimientos y/o funciones que forman parte del componente. El usuario del componente los utiliza para realizar una determinada accin o para obtener un valor determinado al que no se puede acceder por medio de una propiedad. Ya que requieren ejecucin de cdigo, los mtodos slo estn disponibles en tiempo de ejecucin.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI A menos que se especifique lo contrario, los campos, propiedades y mtodos que se aaden a un objeto son de tipo publicados (published). Todos los niveles de control de acceso operan a nivel de unidades, es decir, si una parte de un objeto es accesible (o inaccesible) en una parte de una unidad, es tambin accesible (o inaccesible) en cualquier otra parte de la unidad. A continuacin se detallan los tipos de controles de acceso:
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Al declarar parte de un objeto publicado ( published) provoca que la parte sea pblica y adems genera informacin en tiempo de ejecucin para dicha parte. Las propiedades declaradas publicadas aparecen en el inspector de objetos en tiempo de diseo. Y ya que slo las partes publicadas aparecen en el inspector de objetos, estas partes definen el interface en tiempo de diseo de nuestro componente. En general slo se deben declarar publicadas propiedades y no funciones o procedimientos (ya que lo nico que logramos con ello es declararlas pblicas).
TComponent - El punto de partida para los componentes no visuales. TWinControl - El punto de partida si es necesario que el componente disponga de handles. TGraphicControl - Un buen punto de partida para componentes visuales que no sea necesario que dispongan de handles, es decir, que no reciban el foco. Esta clase dispone del mtodo Paint y de Canvas. TCustomControl - El punto de partida ms comn. Esta clase dispone de window handle, eventos y propiedades comnes y, principalmente, canvas con el mtodo Paint.
Bien, ya sabemos como determinar el punto de partida. Veamos ahora como crear la unidad que albergar el componente. Hay dos opciones, crear la unidad manualmente o dejar que Delphi haga el trabajo "sucio" utilizando el experto de componentes. Si optamos por la primera solucin, basta con hacer clic en new unit y ponernos manos a la obra, pero de este modo tendremos que hacer todo a mano: derivar el nuevo componente, registrarlo, etc. Por ello es ms recomendable la segunda opcin: utilizar el experto de componentes.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Para abrir el experto de componentes basta con elegir File|New Component. Nos aparecer un cuadro de dilogo en el que debemos cumplimentar los siguientes campos:
Class Name: Aqu debemos especificar el nombre del nuevo componente. Ancestor type: Introduciremos aqu el ascendiente a partir del cul derivaremos nuestro componente. Palette Page: Indicaremos aqu la pgina de la paleta en la cul queremos que aparezca el nuevo componente.
Una vez introducidos estos campos, al pulsar sobre OK se nos desplegar el cdigo de nuestra unidad. Si por ejemplo hemos introducido los siguientes datos en el experto de componentes: Class Name: TMiComponente Ancestor Type: TComponent Palette Page: Curso Al hacer clic en aceptar, Delphi nos generara la siguiente unidad, lista para introducir las propiedades y mtodos de nuestro componente:
unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TMiComponente = class(TComponent) private { Private declarations } protected { Protected declarations } public { Public declarations } published { Published declarations } end; procedure Register; implementation procedure Register; begin RegisterComponents('Curso', [TMiComponente]); end; end.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI A partir de aqu todo consiste en introducir las propiedades y mtodos necesarios para el funcionamiento de nuestro componente. Pero antes de nada conviene hacer notar algunos aspectos: En la clausula uses, Delphi aade por defecto las unidades estandard. Si nuestro componente no usa alguna de ellas podemos eliminarla de dicha clausula. Del mismo modo, si utilizamos algn procedimiento o funcin situado en otra unidad, debemos aadir dicha unidad a la clausula uses. Vamos, como siempre no? ;) Las declaraciones de las propiedades, campos y mtodos que vayamos definiendo, las situaremos en la seccin apropiada de interfaz segn corresponda, es decir las declararemos privadas, protegidas, pblica o publicadas. Delphi declara y define automaticamente el procedimiento register para registrar el componente en la paleta.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI poder introducir la letra manualmente, ya que debe ser el propio componente el que la calcule. Esta propiedad debe de ser pblica (public) ya que es de slo lectura. Este aspecto conviene resaltarlo: las propiedades de slo lectura deben ir declaradas en la parte pblica. La funcin que se encarga de calcular la letra del NIF la llamaremos GetNIF. Los campos que almacenan el valor de propiedades siempre se declararn en la parte privada (private) ya que as nos aseguramos de que el componente que declara la propiedad tiene acceso a ellos, pero el usuario del componente no, ya que l debe acceder a travs de la propiedad y no del campo (que representa el almacenamiento interno de la propiedad). Respecto a los nombres empleados, se siguen las siguientes convenciones:
Los tipos que definamos comenzarn con la letra T (de tipo). P.e. TNif Los campos que almacenan los valores de propiedades comienzan con la letra F seguida del nombre de la propiedad que almacenan. As el campo FDNI queda claro que almacena el valor de la propiedad DNI. Los nombres de los mtodos de lectura y escritura de los valores de una propiedad se denominarn mediente el prefijo Get (para lectura) o Set (para escritura) seguidos del nombre de la propiedad. P.e. el mtodo GetNIF.
Utilizamos el experto de componentes para que nos generere la unidad en la que escribiremos nuestro componente. En el cuadro de dilogo que el experto nos muestra, introducimos TNif como Class Name, TComponent como Ancestor Type y Curso como Palette page. Al pulsar sobre OK, el experto de componentes nos crea el esqueleto bsico de nuestro componente, incluyendo el procedimiento Register. De todas las unidades que aparecen en la clausula uses dejamos slo Classes, ya que no utilizamos ningn procedimiento de las dems. Declaramos el campo FDNI (longInt) en la seccin privada. En este campo, como ya se ha dicho, almacenaremos el DNI. En la seccin published escribimos la siguiente lnea: property DNI: LongInt read FDNI write FDNI De este modo declaramos la propiedad DNI y especificamo s que la lectura y escritura de valores en la misma se hace directamente con el campo FDNI utilizando el inspector de objetos. Declaramos la propiedad NIF de slo lectura en la parte public. Conviene recordar que las propiedades de slo lectura deben ir declaradas en la parte pblica y no en la publicada. Especificamos que para leer el valor de la propiedad utilizaremos la funcin GetNIF, la cul declaramos en la seccin privada. Escribimos la funcin que clcula la letra del NIF en la parte de implementacin de la unidad. Guardamos la unidad con el nombre nif.pas. Conviene que todos los componentes que vayamos creando durante el curso los almacenemos en un directorio aparte (p.e delphi\componen)
Manual de Creacin de Componentes en Delphi MBS para el LTIASI maysculas) debe coincidir con el nombre del componente. Los dos ficheros (el de la unidad *.pas y el de el bitmap *.dcr) deben residir en el mismo directorio. En nuestro componente, si hemos salvado la unidad con el nombre nif.pas nuestro archivo de recursos deber tener el nombre nif.dcr. Dentro de este archivo se encontrar el bitmap, al que pondremos el nombre TNIF. El bitmap que hemos creado es el siguiente: El tamao del bitmap debe ser de 24x24 pixels.
Como ltimo detalle, si quieres utilizar este mismo bitmap, puedes utilizar un programa de tratamiento de imgenes para cortarlo y pegarlo en el editor de imgenes de Delphi.
Elegir la opcin Options|Install Components desde el men de Delphi. Pulsar sobre el botn Add para seleccionar la unidad que contiene el cdigo fuente del componente. En nuestro caso, nif.pas. Una vez seleccionada la unidad, hacer clic sobre OK. Delphi compilar la unidad y reconstruir el archivo COMPLIB.DCL. Si se encuentran errores al compilar, se nos informar de ello. Basta con corregir el error, salvar la unidad y repetir el proceso anterior. Si la compilacin tiene xito, nuestro componente entrar a formar parte de la paleta de componentes.
Nota: Es una buena idea antes de comenzar hacer una copia de seguridad del archivo COMPLIB.DCL para evitar posibles problemas derivados de fallos al compilar, cadas de tensin, etc.
Aadir la unidad en que se define el componente a la clasula uses de nuestra unidad. En nuestro caso, como el objeto timer se declara en la unidad ExtCtrls, es esta unidad la que debemos aadir a la clasula uses. Aadir al mtodo create de nuestro componente el cdigo necesario para construir el objeto (en nuestro caso, el timer). Escribir el cdigo necesario para destruir el objeto en el mtodo destroy de nuestro componente.
Constructores y destructores.
Constructores Los objetos que declaramos en un componente no existen en memoria hasta que el objeto es creado (tambin se dice que se crea una instancia del objeto) por una llamada al mtodo constructor del objeto. Un constructor no es ms que un mtodo que proporciona memoria para el objeto y apunta (mediante un puntero) hacia l. Llamando al mtodo create , el constructor asigna la instancia del objeto a una variable.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Conviene hacer notar que todos los componentes heredan un mtodo denominado create que se encarga de "crear" (en el sentido que acabamos de ver) el componente en memoria. De forma adicional, en el mtodo create se pueden inicializar los valores de determinados campos del componente (comnmente asignarles un valor por defecto) En el mtodo create del componente podemos crear objetos adicionales que necesite nuestro componente. En nuestro caso, en el mtodo create de TBlinkLbl crearemos el timer. Destructores Cuando terminamos de utilizar un objeto, debemos destruirlo, es decir, liberar la memoria que ocupaba el objeto. Esta operacin se realiza mediante el mtodo destroy que todos los componentes heredan de TComponent. Si hemos creado algn objeto adicional, tambin debemos destruirlo escribiendo el cdigo necesario en el mtodo destroy. Delphi crea y destruye automaticamente nuestro componente cuando es necesario, ya que como se ha dicho, los mtodos create y destroy se heredan de TComponent. Pero si queremos utilizar algn objeto en nuestro componente, Delphi no lo crea y destruye automaticamente, sino que debemos hacerlo nosotros de forma manual. Este proceso se realiza aadiendo el cdigo necesario a los mtodos create y destroy. Es importante notar que nos se escriben los mtodos de nuevo, sino que se sobrecargan con el nuevo cdigo. De una forma general esto se hace de la siguiente forma: (un ejemplo ms concreto lo tenemos en el propio cdigo fuente del componente)
override; TComponent); begin inherited Create(AOwner); ... end; destructor TComponent.Destroy; begin ... inherited destroy; end; destructor destroy; override;
En la parte pblica de nuestro componente declaramos los mtodos create y destroy seguidos de la palabra clave overrride:
public constructor create(AOwner : TComponent);
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Lo ms importante a tener en cuenta es que al sobrecargar un constructor, lo primero que se debe hacer es hacer una llamada al constructor original (lnea inherited Create). A continuacin se puede aadir el cdigo necesario para crear el objeto. De un modo similar, al sobrecargar un destructor, primero se debe liberar el objeto que hayamos creado anteriormente y la ltima lnea debe de ser una llamada al destructor original.
Implemementando el parpadeo.
En nuestro proceso de diseo del componente nos queda an un paso importante: cmo hacer que el mensaje parpadee. Hemos dicho que cuando se produzca el evento OnTimer se ejecutar el mtodo parpadea que se encargar de visualizar y ocultar el mensaje, es decir, de producir el parpadeo. La solucin es muy simple, ya que nuestro componente, al descender de TLabel incorpor una propiedad que determina si el componente debe estar visible o no. Esta propiedad se denomina visible . De este modo, nuestro mtodo parpadea lo nico que har ser alternar el valor de la propiedad booleana visible segn se produzca el evento OnTimer. Los detalles concretos de implementacin se muestran en el cdigo fuente.
Hemos coment ado que si el valor introducido como frecuencia de parpadeo es nulo, el mensaje debe permanecer fijo en pantalla. Esto se logra activando y desactivando el componente cuando se introduce el valor de la propiedad velocidad en el inspector de objetos (ver el procedimiento SetVelocidad en el cdigo fuente). De este modo, si se introduce 0 como valor de la velocidad, simplemente se deshabilita el timer y se pone el valor de la propiedad visible a True. Si el valor introducido es distinto de cero, se habilita el timer y, por consiguiente, el parpadeo. Un ltimo detalle es dar un valor por defecto a la propiedad velocidad de parpadeo. Esto se consigue en dos pasos: primeramente en la declaracin de la propiedad velocidad se aade la palabra clave default seguida del valor por defecto (400 en nuestro caso). A continuacin, en el constructor se inicializa el campo asociado con la propiedad (FVelocidad:=400). Los dos pasos son necesarios , no pudiendose obviar ninguno de los dos. Este detalle de asignar valores por defecto es ms importante de lo que parece a simple vista, ya que de este modo nos evitamos errores de inicializacin,
Manual de Creacin de Componentes en Delphi MBS para el LTIASI asegurndonos que la propiedad siempre tendr (incluso desde el principio) un valor vlido. Adicionalmente, en los forms en que se utilice el componente, Delphi slo guardar el valor de la propiedad si difiere de su valor por defecto.
type TBlinkLabel = class(TLabel) {TBlinkLabel deriva de TLabel} private FVelocidad : integer; {Frecuencia de parpadeo} FTimer : TTimer; {Timer para la frecuencia} procedure SetVelocidad(valor : integer); {Almacena la velocidad} protected procedure parpadea(Sender : TObject); public constructor Create(AOwner : TComponent); override; {Constructor} destructor Destroy; override; {Destructor} published property Velocidad : integer read FVelocidad write SetVelocidad default 400; end; procedure Register; implementation constructor TBlinkLabel.Create(AOwner : TComponent); begin inherited Create(AOwner); {Llama al constructor original (heredado)} FTimer := TTimer.Create(Self); {Creamos el timer} FVelocidad := 400; {Frecuencia (velocidad) por defecto} FTimer.Enabled:=True; {Activamos el timer} FTimer.OnTimer:=parpadea; {Asiganamos el mtodo parpadea} FTimer.Interval:=FVelocidad; {Asignamos el intervalo del timer = frecuencia parpadeo} end; destructor TBlinkLabel.Destroy; begin FTimer.Free; {Liberamos el timer} inherited destroy; {Llamamos al destructor original (heredado)} end; procedure TBlinkLabel.SetVelocidad (valor : integer); begin If FVelocidad <> valor then {Slo si el valor introducido es distinto del almacenado} begin if valor < 0 then FVelocidad:=0; FVelocidad:=Valor; {Asigna la velocidad}
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Tradicionalmente, la programacin grfica en windows se ha realizado mediante el uso del interface grfico de dispositivo (GDI). Este interface es una herramienta poderosa que permite el dibujo de grficos mediante el uso de pinceles, brochas, rectngulos, elipses, etc. Pero el GDI tiene un inconve niente: su programacin es muy laboriosa. Cuando se va a hacer uso de una fucin GDI, se necesita un handle a un contexto dispositivo, asi como crear y destruir las diversas herramientas de dibujo (recursos) que se utilicen. Pr ltimo, al finalizar, se debe restaurar el contexto de dispositivo a su estado original antes de destruirlo. Delphi encapsula el GDI, haciendo que no nos tengamos que preocupar de contextos de dispositivo ni de si hemos liberado o no los recursos utilizados. De este modo nos podemos centrar en lo que es nuestro objetivo principal: dibujar los grficos. De todas formas, si lo deseamos, podemos seguir utilizando las funciones GDI si nos interesa. As tenemos un doble abanico de posibilidades, Delphi o GDI, que utilizaremos segn nos convenga. Como se ha mencionado en el prrafo anterior, Delphi nos proporciona un completo interface grfico. El objeto principal de este interface es el objeto Canvas . El objeto Canvas se encarga de tener un contexto de dispositivo vlido, as como de liberarlo cuando no lo utilicemos. Adems, el objeto Canvas (o simplemente Canvas) dispone de diversas propiedades que representan el lapiz actual (pen), brocha (brush) y fuente (font). El canvas maneja todos estos recursos por nosotros, por lo cual nos basta con informarle de que clase de pen queremos manejar y l se encarga del resto. Adems, dejando que Delphi se encargue de crear y liberar los recursos grficos, en muchos casos obtendremos un aumento de velocidad frente a si los manejramos nosotros mismos. El objeto canvas encapsula la programacin grfica a tres niveles de profundidad, que son los siguientes: Nivel Operacin Dibujo de lneas y formas Visualizacin y modificacin de texto Relleno de reas Herramientas Mtodos MoveTo, LineTo, Rectangle, Ellipse Mtodos TextOut, TextHeight, TextWidth, TextRect Mtodos FillRect, FloodFill Propiedades Pen, Brush y Font Propiedad Pixels Draw, StretchDraw, BrushCopy, CopyRect, CopyMode Propiedad Handle
Alto
Personalizar texto y grficos Intermedio Manipulacin pixels Copia y unin de imgenes Bajo Llamadas a funciones GDI
Ahora no nos vamos a centrar en explicar todos y cada uno de las herramientas disponibles para la creacin de grficos, sino que lo haremos segn vayamos
Manual de Creacin de Componentes en Delphi MBS para el LTIASI utilizndolas en sucesivas unidades. De todas formas, una descripcin detallada de cada una de ellas se encuentra en la ayuda en lnea de delphi Bien, ya conocemos que el objeto canvas nos proporciona un interface grfico y poderoso de creacin grfica. Pero ahora se nos pueden plantear las siguientes preguntas: Donde reside el objeto canvas?, todos los componentes lo tienen?, y si no es as, qu componentes lo incorporan? La respuesta a estas preguntas es sencilla: Todos los objetos derivados de TGraphicComponent poseen Canvas . Respecto a otros componentes, depende. En caso de duda, lo ms sencillo es consultar el Object Browser y verificar si el componente en cuestin posee o no canvas (bien sea declarado de forma protegida o pblica). As por ejemplo, los componentes Tlabel, Timage y TPanel lo poseen, mientras que componentes tales como TButton o TRadioButton no. Este aspecto determinar en gran medida el ancestor que hemos de elegir cuando queramos desarrollar un componente que deba disponer de Canvas. La forma de acceder al canvas de un objeto es muy sencilla. Supongamos que tenemos un componente (TGradiente) derivado de la clase TGraphicControl. Y supongamos que queremos dibujar en el canvas una lnea desde el punto (0,0) hasta el punto (20,20). Para realizar esta tarea deberiamos escribir la siguiente sentencia:
TGradiente.Canvas.MoveTo(0,0); TGradiente.Canvas.LineTo(20,20);
Centremonos ahora en las propiedades del canvas que utilizaremos en el desarrollo de nuestro componente. Bsicamente TGradiente debe dibujar una serie de rectngulos coloreados con un determinado color. Para ello utilizaremos el mtodo FillRect. FillRect recibe como parmetro un objeto del tipo TRect con las coordenadas superior izquierda e inferior derecha del recuadro a pintar. De esta forma, el cdigo que utilizaremos en nuestro componente ser similar al siguiente (an no tenemos en cuenta el color de relleno)
Canvas.FillRect(TRect);
Para asignar el color de relleno utilizaremos la propiedad brush. El objeto brush determina determina el color y patrn que el canvas utiliza para rellenar formas grficas y fondos. En nuestro caso, utilizaremos la propiedad color del brush para asignar el color de relleno del canvas, de modo que una posterior llamada a FillRect utilice dicho color asignado. Tambien necesitaremos utilizar la propiedad pen del canvas. El objeto pen determina que clase de lapiz utilizar el canvas para dibujar lneas, puntos y recuadros grficos. En nuestro caso, utilizaremos la propiedad style del pen que determina que tipo de lnea dibujar (en nuestro componente psSolid) y la propiedad mode que determina el modo con que el lapiz dibujar sobre el canvas (en nuestro componente pmCopy)
Manual de Creacin de Componentes en Delphi MBS para el LTIASI En Delphi, un color se representa por 4 bytes hexadecimales. El byte ms alto se utiliza para determinar el ajuste que Windows hace de la paleta y no vamos a verlo con ms detalle. Los otros tres bytes (los tres bytes ms bajos) representan la cantidad de rojo, verde y azul que forman el color. Nuestro componente debe calcular un gradiente de colores desde uno inicial (FColorDesde en el cdigo fuente) hasta otro final (FColorHasta). La mejor manera de calcular este gradiente es descomponiendo dichos colores en sus componentes RGB (Rojo, Verde y Azul). De este modo, sabremos la cantidad de cada uno de estos tres colores bsicos que forman un color determinado. Por ejemplo el color rojo puro se representa por 255,0,0 (255 de rojo, 0 de verde y azul), y un tono gris tiene los tres valores de rojo, verde y azul iguales (p.e. 150,150,150). La descomposicin de un color en sus tres componentes bsicos la realizan tres funciones: GetRValue, GetGValue y GetBValue pertenecientes al API de Windows. Estas funciones toman como parmetro el color del que deseamos obtener la descomposicin y devuelven respectivamente la "cantidad" de rojo, verde y azul que componen dicho color. En el cdigo fuente, una vez descompuestos los colores inicial y final en las variables RGBDesde[0..2] y RGBHasta[0..2] (0 para color rojo, 1 para verde y 2 para azul), el proceso de calculo del gradiente es el siguiente: 1. Calcular la diferencia en valor absoluto entre RGBDesde y RGBHasta para cada uno de los colores bsicos y guardarla en la variable RGBDif[0..2]. Adicionalmente, en la variable factor guardaremos un +1 si RGBHasta es mayor que RGBDesde (gradiente ascendente) y un -1 en caso contrario (gradiente descendente). Este proceso se realiza para cada uno de los tres colores bsicos. 2. Mediante un bucle que vara desde 0 a 255 (nuestro gradiente constar de 256 colores), calculamos el color que corresponde a cada recuadro a dibujar (con el mtodo FillRect) mediante la expresin:
Rojo:=RGBDesde[0]+factor[0]*MulDiv(contador,RGBDif[0],255);
(de forma anloga para el verde y el azul) Nota: MulDiv es una funcin del Api de Windows que multiplica los dos primeros valores pasados como parmetros y el resultado se divide por el tercer parmetro, devolviendo el valor de esta divisin en forma de 16 bits redondeado.
Como es fcil suponer, la funcin RGB toma tres valores de rojo, verde y azul, y forma el color correspondiente a dichos colores bsicos.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Banda es una variable de tipo TRect que guarda las coordenadas del recuadro a dibujar.
5. Se prosigue con otra iteracin del bucle. Un ltimo detalle: cmo se ha mencionado, nuestro gradiente constar de 256 colores. Sin embargo, dependiendo del modo grfico en que tengamos configurado Windows y de la existencia de otros objetos con sus propios colores, Windows ajustar los colores disponibles para que el resultado final sea lo ms aproximado posible al pedido.
Es importante hacer notar dos aspectos: el primero es que una redeclaracin slo puede hacer menos restrictivo el acceso a una propiedad (p.e paso de protected a public), pero no ms restrictivo (p.e paso de public a protected). El segundo aspecto es que al redeclarar, no es necesario especificar el tipo de la propiedad, basta con indicar su nombre. Lo que si podemos hacer en el momento de la redeclaracin es definir un nuevo valor por defecto para dicha propiedad. El resto de los detalles del cdigo fuente no creo que merezca la pena comentarlos, ya que la teora y prctica de los conceptos mostrados ya se ha visto en anteriores unidades. Se declaran las propiedades Direccion, ColorDesde y ColorHasta y se escriben los mtodos necesarios para almacenar los valores correspondientes... De todas formas, si alguien tiene dudas, ya sabe, me escribe donde siempre.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI multilinea que nos permitir mostrar ms de una lnea de texto en cada celda del grid (rejilla). La figura que sigue es un ejemplo del componente en funcionamiento:
Definiendo la alineacin de un modo global. Si nos decidiesemos por esta implementacin, bastara con definir una propiedad (denominada, logicamente, Alignment) que controlara la alineacin de todas las celdas del grid. Esto sera muy sencillo, pero muy poco prctico, ya que lo normal es que queramos tener, por ejemplo, las celdas de cabecera centradas, las de texto alineadas a la izquierda, las nmericas a la derecha, etc. Una solucin un poco mejor: definir la alineacin a nivel de columnas. De este modo cada columna puede estar alineada con independencia de las dems, pero persiste el tema de que las filas de una misma columna deben tener la misma alineacin (cabecera y datos). Un problema aadido es que para implementar esta eleccin deberamos crearnos un editor de propiedades, y an no sabemos como hacerlo (pero paciencia, que en prximas unidades se ver). Este es el caso del editor de columnas que incorpora Delphi 2. La tercera posibilidad nos ofrece un control total: definir la alineacin de cada celda a nivel individual. De este modo cada celda tendr su propia alineacin con independencia de las dems. La pega es que, como ms adelante veremos, requiere un poco ms de esfuerzo por parte del usuario del componente.
Como se ve, cada una de las tres implementaciones tiene sus ventajas e inconvenientes. En nuestro componente vamos a implementar una combinacin del primer y tercer mtodo. De este modo, definiremos una propiedad Alignment que especificar la alineacin por defecto de las celdas del grid y al mismo tiempo, mediante un nuevo evento, se podr determinar la alineacin de celdas a nivel individual. La implementacin de la alineacin horizontal a nivel global no tiene ningn misterio: basta definir la propiedad Alignment de tipo TAlignment (tipo ya incluido en Delphi). El campo que guardar el valor de esta propiedad se denominar FAlignment. Para escribir el valor de la propiedad utilizaremos el mtodo SetAlignment, mientras que la
Manual de Creacin de Componentes en Delphi MBS para el LTIASI lectura de la propiedad se har directamente del campo FAlignment. De este modo tenemos perfectamente definido la interfaz de la propiedad Alignment. Respecto a cmo dibujaremos el contenido de la celda con la alineacin apropiada lo dejaremos para ms tarde, cuando estudiemos el evento OnDrawCell. La alineacin vertical se desarrolla de una forma similar. El nico aspecto que conviene hacer notar es que Delphi no incorpora un tipo TVerticalAlignment, de modo que debemos crearlo nosotros:
TVerticalAlignment = (vaTop, vaCenter, vaBottom);
Nos queda ver como implementamos la interfaz de la alineacin de las celdas a nivel individual. Para ello vamos a crearnos un nuevo evento, que se disparar cada vez que necesitemos saber el estado de alineacin de una celda en concreto. Cmo ya se vi en la unidad 2, un evento (tambin denominado suceso) es un mecanismo que vncula una accin a cierto cdigo. Ms concretamente, un evento es un puntero a mtodo, un puntero que apunta a un mtodo especfico de una instancia de objeto especfica toma ya que impresionante!;) La forma de implementar un evento se hace mediante propiedades, es decir, los eventos son propiedades. A diferencia de las propiedades estndard, los eventos no utilizan mtodos para implementar las partes read y write. En su lugar, las propiedades de eventos utilizan un campo privado del mismo tipo que la propiedad. Pero basta de teora y manos a la obra. Como ya se ha mencionado, vamos a crear un nuevo evento que se debe disparar cada vez que necesitemos obtener el valor de la alineacin de una celda especfica. Lo primero que debemos hacer, por tanto, es definir nuestro tipo de suceso. Esto lo hacemos mediante la siguiente sentencia:
TMultiGridAlignmentEvent=procedure(Sender:TObject; ARow,ACol:LongInt; var HorAlignment: TAlignment; var VerAlignment: TVerticalAlignment) of object;
Sender: Que identifica el objeto que hace la llamada. ARow y ACol : Que identifican la celda de la que se pide la informacin de alineacin. var HorAlignment y var VerAlignment: Parmetros de tipo var en el que el usuario del componente debe devolver la alineacin horizontal y vertical correspondiente a la celda. Ver el cdigo de ejemplo al final de la unidad par ver una demostracin del uso que un usuario puede hacer de este evento.
Convie ne hacer notar las palabras of object al final de la declaracin del tipo de evento. Ya hemos definido el tipo de evento. Ahora debemos crear un campo que guarde el estado de la propiedad OnGetCellAlignment. Esto lo realizamos en la parte private:
private ... FOnGetCellAlignment : TMultiGridAlignmentEvent;
Slo nos queda disparar el vento cuando lo necesitemos. Aunque un poco ms adelante veremos con ms detalle cuando queremos hacerlo en nuestro componente, el fragmento que sigue nos permite ver el mecanismo general:
if Assigned(FOnGetCellAlignment) then FOnGetCellAlignment(Self,ARow,ACol,HorAlineacion,VerAlineacion);
Importante: Antes de activar un evento, conviene mirar primero si dicho evento tiene un manejador de evento asignado, ya que el usuario del componente no tiene porque haber escrito dicho manejador. De ah la comparacin if Assigned : si hay un manejedor de evento, se le llama, pero si no lo hay no se hace nada.
La diferencia principal entre este evento y el correspondiente a la alineacin viene dada por los parmetros ABruh y AFont. El usuario del componente debe devolver el brush y font correspondientes a la celda en concreto referenciada por ARow y ACol. El parmetro AState nos informa del estado de la celda (seleccionada, enfocada, fija...)
Celdas Multilnea.
Pasemos ahora a la implemantacin de celdas multilnea. Esta es una caracterstica que se echa mucho de menos en el StringGrid estndard y que, como vamos a ver, no nos va a costar casi nada implementar. En primer lugar definiremos la interfaz. Para ello, vamos a crear una nueva propiedad denominada MultiLinea (original, verdad? ;) ). Dicha propiedad se almacenar en el campo FMultiLinea que ser de tipo boolean. Si FMultiLinea est a false, nuestro componente se comportar como el StringGrid normal, mientras que si est a true se permitirn las celdas multilnea.
private FMultiLinea : Boolean; ...
Conviene hacer notar que en el mtodo SetMultiLinea, si se asigna un nuevo valor a FMultiLinea se provoca el repintado del componente mediante la instruccin Invalidate. Esta misma tcnica se utiliza en los mtodos SetAlignment, SetVerticalAlignment y SetColor. Nos queda por ver cmo dibujaremos las celdas multilnea. Este aspecto lo trataremos en la siguiente seccin.
ACol y ARow: Que identifican a la celda que se debe dibujar en pantalla. ARect : Estructura de tipo rectangular que identifica la esquina superior izquierda y la inferior derecha (en pixels) de la celda a dibujar. AState: Es el estado actual de la celda (seleccionda, enfocada o fija). En principio no utilizaremos el valor pasado en este parmetro.
Ya sabemos donde. Ahora falta por ver cmo. En principio podra asustarnos todo lo que tenemos que hacer: calcular la alineacin horizontal y vertical, activar los eventos de alineacin y color, fragmentar el contenido de la celda en varias lneas... Pero no hay motivo: Delphi y el Api de windows vienen en nuestra ayuda y toda esta codificacin se reduce a 20 o 30 lneas fcilmente entendibles. A continuacin se muestra el cdigo correspondiente al mtodo DrawCell, el cal paso a comentar:
procedure TMultiGrid.DrawCell(ACol,ARow : LongInt; ARect : TRect; AState : TGridDrawState); Const TextAlignments : Array[TAlignment] of Word = (dt_Left, dt_Right, dt_Center); Var HorAlineacion : TAlignment; VerAlineacion : TVerticalAlignment; Texto : string; Altura : integer; CRect : TRect; opciones : integer; begin Texto:=Cells[ARow,Acol]; HorAlineacion:=FAlignment; VerAlineacion:=FVerticalAlignment; if Assigned(FOnGetCellAlignment) then FOnGetCellAlignment(Self,ARow,ACol,HorAlineacion,VerAlineacion); if Assigned(FOnGetCellColor) then FOnGetCellColor(Self,ARow,ACol,AState,Canvas.Brush,Canvas.Font);
Lo primero que hacemos es guardar el contenido de la celda a dibujar en la variable Texto (de tipo string). A continuacin se dan los valores por defecto a las variables HorAlineacion y VerAlineacion debido a que si el usuario no ha introducido una alineacin particular para la celda en cuestin se aplicarn estas alineaciones. Ahora viene una de las claves: la llamada a los eventos. Si el usuario ha escrito un manejador para el evento Alineacin, se llama. Lo mismo ocurre para el evento Color. De este modo ya tenemos el tratamiento especfico de la celda. Lo siguiente es dibujar el fondo de la celda mediante el mtodo FillRect al que pasamos el recuadro de dibujo de dicha celda (ARect). Necesitamos ahora hacer una pausa para explicar cmo vamos a dibujar el texto. A primera vista, lo lgico sera utilizar el mtodo TextOut del objeto Canvas. Este mtodo necesita como parmetros las coordenadas (x ,y) donde dibujar el texto y una cadena con el texto a dibujar. Pero para nuestros propsitos se queda corto, ya que tendramos que calcular a mano la posicin de dibujo para que la alineacin fuera la correcta. Adems tendramos que calcular las divisiones de palabras necesarias para las celdas multilneas, etc. En fin, un rollo. Y en mi opinin si podemos evitarnos todo este trabajo, pues mejor, no? ;) Y lo bueno es que... podemos!!!. El secreto: una funcin del Api de windows: DrawText DrawText necesita los siguientes parmetros:
Un handle al objeto en que queremos dibujar (el canvas del grid). Una cadena de tipo terminada en nulo con el texto a dibujar (Pchar(Texto))
Un nmero que indica el nmero de carcteres a dibujar (-1 para todos) El rectngulo en el que hay que formatear y dibujar el texto (TRect) Una serie de opciones de formateo del texto. Nosotros vamos a utilizar las que controlan la alineacin del texto (dt_Left, dt_Center, dt_Right, dt_VCenter), el troceo de palabras (dt_WordBreak) y el clculo de la altura (dt_CalcRect)
Hay ms opciones para DrawText, de modo que si quieres ms informacin slo tienes que mirar en la ayuda en lnea. En la prctica necesitamos hacer dos llamadas a DrawText, una primera en la que al incluir la opcin dt_CalcRect no se dibuja nada en pantallas, sino que slo se calcula la altura requerida del rectngulo para el troceo de palabras (multilnea). Esta altura la guardamos en la variable Altura. Es importante hacer notar que el parmetro Rect pasado cmo parmetro se modifica, por lo que necesitaremos previamente guardar su estado. Posteriormente, una vez reajustado el rectngulo se hace una segunda llamada sin la opcin dt_Calcrect que es la que de verdad dibuja el texto en el canvas. Volvemos ahora al flujo del programa. Despues de rellenar el fondo de la celda con FillRect, copiamos en la variable CRect el rectngulo original (ARect) y se preparan las opciones con que vamos a llamar a DrawText. Aqu surge un pequeo problema, ya que HorAlineacin es del tipo TAlignment (ta_LeftJustify...) y DrawText no entiende este tipo, por lo que es necesario una conversin entre dicho tipo y el que DrawText entiende. Esta conversin la llevamos a cabo mediente una matriz constante denominada TextAlignments. A continuacin, si la propiedad Multilinea est a true, se aade dt_WordBreak a las opciones de DrawText. Lo que se hace a continuacin es verificar si el usuario ha tocado el valor de la propiedad DefaultDrawing. Si el valor de esta propiedad es false indica que es el usuario el que se encarga de todo el proceso, en caso contrario, el componente se encarga del dibujo. Si es el componente el que se encarga de todo (para eso lo queremos, no?) hacemos la primera llamada a DrawText para obtener la altura requerida del rectngulo de celda. Con esta altura y, siempre en cuando sea posible (todo el texto multilnea quepa en la celda), se pasa a centrar el texto en la celda (o a poner a top o bottom segn corresponda). Una vez hecho esto, volvemos a llamar a DrawText para que se produzca, ahora si, el dibujado del texto en pantalla. Ojo a que en esta ocacin pasamos ARect como rectngulo. Esto es lgico, ya que nos nos podemos salir del rectngulo que tiene la celda. Y voil!!! hemos terminado. Este es a grandes rasgos el funcionamiento del mtodo DrawCell. Conviene que mires y remires el mismo hasta que lo llegues a entender ya que es extremadamente potente y puede servirte para otras ocasiones. Y si tienes dudas, ya sabes, me escribes ;)
Otros detalles.
Por ltimo quiero mencionar algunos pequeos detalles:
Se redefine la propiedad Options del StringGrid estndard para poner por defecto a true las opciones goRowSizing y goColSizing ya que al ser una rejilla
Manual de Creacin de Componentes en Delphi MBS para el LTIASI multilnea lo ms normal es que estas opciones esten siempre a True y en el grid estndard estn a false. Como siempre, en el constructor declaramos los valores por defecto para las distintas propiedades que definimos (alineacin, color, multilnea, options). Despus del cdigo fuente se muestra un ejemplo de los eventos alineacin y color que podra escribir un usario del componente. Este ejemplo da lugar al grid que se muestra al principio de la unidad.
Ejemplo de utilizacin.
A continuacin se muestra un ejemplo de utilizacin de nuestro nuevo componente. Por brevedad, slo se muestra el cdigo correspondiente a los eventos FormCreate y GetCellCOlor y GetCellAlignment. Por supuesto, este no es un jemplo real, ya que por simplicidad, el contenido de las celdas se determina en el FormCreate cuando lo normal es que dicho contenido venga de otra parte (de clculos, bases de datos, etc.). Pero como muestra de uso es suficiente.
procedure TForm1.FormCreate(Sender: TObject); begin MultiGrid1.Cells[0,1]:='Enero'; MultiGrid1.Cells[0,2]:='Febrero'; MultiGrid1.Cells[0,3]:='Total Ao'; MultiGrid1.Cells[0,4]:='Notas'; MultiGrid1.Cells[1,0]:='Zona A'; MultiGrid1.Cells[2,0]:='Zona B'; MultiGrid1.Cells[3,0]:='Resto de Zonas'; MultiGrid1.Cells[4,0]:='TOTAL'; MultiGrid1.Cells[1,1]:='1.000.000'; MultiGrid1.Cells[1,2]:='1.250.000'; MultiGrid1.Cells[1,3]:='9.150.000'; MultiGrid1.Cells[1,4]:='Incremento sobre ao anterior'; MultiGrid1.Cells[2,1]:='1.450.000'; MultiGrid1.Cells[2,2]:=' 950.000';
Manual de Creacin de Componentes en Delphi MBS para el LTIASI componente en el form, asignar sus propiedades, llamar al mtodo execute y el resto del trabajo lo har el propio componente. Esta conversin de cuadros de dilogo en componentes es una opcin muy potente ya que nos permite reutilizar el cdigo de nuestros diversos proyectos de una forma rpida y sencilla. Seguro que en cuanto conozcamos la tcnica de cmo lograrlo se nos ocurrirn infinitas posibilidades para convertir pantallas que utilizamos a menudo en componentes. Baste con decir que yo tengo un componente que encapsula toda una aplicacin de 10 o 12 pantallas!. Hay que hacer notar que en el desarrollo del form utilizo algunas funciones de Delphi 2 que no existen en Delphi 1, con lo cal el cdigo fuente no es directamente compilable en Delphi 1. Si alguno quiere convertirlo a 16 bits no debe tener demasiados problemas y puede ser un buen ejercicio ;)
Mediante dos combo box el usuario podr elegir la tabla a mostrar mediante la seleccin de un alias y de una tabla de dicho alias.
Se mostrar un dbgrid de la tabla seleccionada pudiendo este grid ser editable o no en base a una propiedad del componente. En un panel adicional se podrn realizar bsquedas sobre cualquier campo de la base de datos mediante la seleccin del texto a buscar y del campo correspondiente. El grid tiene asociado un popup menu que permite o no el filtrado de registros en funcin de otra propiedad del componente. (Este menu popup no aparece en la imagen enterior).
El proceso de creacin de nuestro componente lo vamos a dividir en los siguientes pasos, los cuales iremos viendo en detalle en las siguientes secciones:
Primero, crearemos de forma visual el form, teniendo en cuenta que debemos dotarle de un mecanismo de conexin con el componente propiamente dicho. A continuacin crearemos el componente, incluyendo propiedades, mtodos, etc. Por ltimo realizaremos la conexin entre el componente y el form.
Lo primero que conviene hacer notar es la seccin pblica. En ella definimos tres variables de tipo booleano: FReadOnly, FFiltrar y FBuscar. Estas tres variables forman el interface entre el form y el componente. As, a la propiedad ReadOnly que implementaremos en el componente le corresponde la variable FReadOnly del form y, de forma anloga ocurre con las propiedades PermitirBucar (AllowSearch) y PermitirFiltrar (AllowF ilter) del componente. Ser el componente, y no el form quin en su mtodo execute asignar los valores correspondientes a dichas variables. Conviene que para seguir la explicacin que viene ahora tengas a la vista el cdigo fuente completo del form ya que voy a hacer referencias contnuas al mismo. El mtodo Inicializar es el que se encarga de ajustar la visualizacin del form segn las variables FReadOnly, FFiltrar y FBuscar. Este mtodo ser llamado por el propio componente una vez asignado el valor a dichas variables y su cometido es el siguiente:
Ajusta la propiedad ReadOnly del grid de acuerdo con el valor de FReadOnly. Muestra o no el panel de bsqueda segn el valor de FBuscar. Asigna o no el men popup al grid de acuerdo al valor de FFiltrar.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Veamos ahora como se efecta la visualizacin de una tabla. En el evento OnCreate del form se comienza por llenar el combo box cbAlias con todos los alias disponibles. A continuacin, de todos los existentes, se selecciona el primero y se fuerza el relleno del combo box cbTables con todas las tablas existentes en el alias (mtodo cbAliasChange). Dicho relleno fuerza la llamada al mtodo cbTablesChange, el cal se encarga de activar la tabla (tambin se aprovecha para llenar el combo box cbFields con los campos de la tabla seleccionada). De este modo, el grid, que ya est conectado al datasource y a la tabla correspondiente muestra dicha tabla. Posteriormente si el usuario cambia la seleccin del alias o de la tabla el proceso se repite y se muestra la nueva tabla seleccionada. Por simplicidad se ha omitido el tratamiento de errores en la apertura de tablas, ya que no es el objetivo de esta leccin. Pero deberas aadir como mnimo un bloque try..except cuando se activa la tabla y actuar en funcin del tipo de error que se puede producir. Por mi parte, como acabo de decir, no he introducido dicho tratamiento y dejo que delphi se las tenga que ver con las excepciones ya que, al fin y al cabo, para eso est, no? ;) Pasamos ahora a la bsqueda de registros. Para realizarla, el mtodo BuscarClick se basa en el contenido del edit eSearch que contiene la cadena a buscar y en el combo box cbFields que tiene el campo por el que hay que buscar. La bsqueda se realiza mediante los mtodos del objeto TTable FindFirst y FindNext, los cuales supongo los conoces de sobra. En todo caso, encontrars informacin sobre los mismos en la ayuda en lnea de Delphi. Nos queda por ver como implementar el filtrado de registros. Hay diversas formas de hacerlo, y me he decantado por utilizar un menu popup que permiter activar, desactivar y aadir nuevas condiciones al filtro. Concretamente el menu tiene las opciones siguientes: aadir una condicin del tipo Campo = valor, Campo > Valor, Campo < valor y Campo <> Valor, activar el filtro y eliminar el filtro. Las diversas condiciones del filtro se unen entre s mediante el operador AND. El mtodo Activar1Click y Eliminar1Click no necesitan demasiada explicacin ya que se limitan a poner la propiedad Filtered del objeto TTable a True o False segn corresponda. Los restantes mtodos del men (Igual1Click...) aaden na nueva condicin al filtro. Para ello, se utiliza el procedimiento AddFilter, que recibe como parmetros el campo sobre el que realizar la nueva condicin de filtro (que es la columna activa del dbgrid al pulsar el botn derecho del ratn) y el operador (=,<,> o <> segn corresponda. Dicho mtodo, despus de formatear correctamente la cadena, la aade a la propiedad filter del objeto TTable y efecta un refresh para que el nuevo filtro se active. Y con esto acabamos la parte correspondiente al diseo del form. Fcil, verdad? De todos modos, si tneis alguna duda, el cdigo fuente est bastante documentado. Y si an as no lo veis claro, ya sabeis, le dais al teclado y me mandis un e-mail.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Empecemos ahora con el componente, el cal es realmente sencillo. Consta de tres propiedades y dos mtodos, los cules, a esta altura del curso no deben tener ningn secreto para vosotros:
La propiedad ReadOnly determinar si el grid del form ser editable o no. Por defecto toma el valor False, es decir, editable. Sus mtodos read y write se limitan a leer y escribir sobre el campo FReadOnly del componente (no confundir con el FReadOnly del form visto en la seccin anterior). La propiedad AllowSearch determina si debe aparecer el panel de bsqueda en el form. Por defecto su valor es True. Nuevamente sus mtodos read y write actan directamente sobre el campo FAllowSearch. La propiedad AllowFilter determina si se permite o no el filtrado de registros en el form. Por defecto, su valor es True. De sus mtodos read y write que os voy a decir que no os imagineis! ;) El constructor se limita a llamar al constructor heredado y asignar los valores por defecto para las propiedades. El mtodo Execute se encargar de crear y mostrar el form y lo veremos en la siguiente seccin.
Y eso es todo. Era sencillo o no? Esta simplicidad os la encontrareis en general siempre que estis transformando un form en componente. Basta con crear unas cuantas propiedades que actuarn sobre el form y definir un mtodo (que por cierto, no tiene porque llamarse execute) para lanzar el form y voila! Ah! que no se os olvide aadir a la clausula uses del componente (en la seccin de implementacin) la unidad del form. En nuestro caso:
... implementation uses frmView; ...
El mtodo comienza por crear el form cuyo propietario ser la aplicacin. A continuacin se realiza la conexin entre el form y el componente. Para ello se van asignado a las variables FReadOnly, FBuscar y FFiltrar del form las correspondientes del componente. Una vez realizada esta asignacin, debemos hacer que el form se visualice correctamente en base a estos valores (recordar que el form se ha creado, pero an no se ha mostrado). Esto se consigue llamando al mtodo Inicializar del form (este mtodo lo vimos en la seccin Diseo del form). Ya slo nos queda mostrar el form de forma modal y "esperar" a que el usuario se canse de jugar con l, momento en el cul aprovecharemos para destruir el form mediante la llamada al mtodo free. Como ltimo aspecto a destacar conviene hacer notar el bloque try..finally que nos asegura que, pase lo que pase, la memoria ocupada al crear el form, se liberar correctamente.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Se muestra a continuacin el cdigo integro de la unidad del form. El fichero dfm junto con el resto del componente lo podeis bajar directamente en formato zip desde el ndice del curso.
unit frmView; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, DB, DBTables, Grids, DBGrids, ExtCtrls, Buttons, Menus; type TfrmVisor = class(TForm) tTable: TTable; dsDataSource: TDataSource; dbGrid: TDBGrid; pTop: TPanel; pSeleccionTabla: TPanel; pBuscar: TPanel; gbTablas: TGroupBox; Alias: TLabel; Tabla: TLabel; cbAlias: TComboBox; cbTables: TComboBox; gbBuscar: TGroupBox; cbFields: TComboBox; eSearch: TEdit; Buscar: TSpeedButton; BuscarSiguiente: TSpeedButton; PopupMenu1: TPopupMenu; Igual1: TMenuItem; Distinto1: TMenuItem; Menor1: TMenuItem; Mayor1: TMenuItem; N1: TMenuItem; Activar1: TMenuItem; Eliminar1: TMenuItem; procedure FormCreate(Sender: TObject); procedure FillcbFields; procedure Inicializar; procedure cbAliasChange(Sender: TObject); procedure cbTablesChange(Sender: TObject); procedure BuscarClick(Sender: TObject); procedure BuscarSiguienteClick(Sender: TObject); procedure dbGridColEnter(Sender: TObject); procedure PopupMenu1Popup(Sender: TObject); procedure Activar1Click(Sender: TObject); procedure Eliminar1Click(Sender: TObject); procedure Igual1Click(Sender: TObject); procedure Distinto1Click(Sender: TObject); procedure Menor1Click(Sender: TObject); procedure Mayor1Click(Sender: TObject); private public FReadOnly, FFiltrar, FBuscar : boolean; end; procedure PascalStr(var s : string); { (c) 1997 by Luis Roche }
Manual de Creacin de Componentes en Delphi MBS para el LTIASI En tiempo de diseo, cuando se selcciona un componente, el inspector de objetos crea instancias de los editores de propiedades necesarios para editar las propiedades definidas en el componente seleccionado. Cuando termina la edicin, el mismo inpector de objetos destruye los editores de propiedades creados. Delphi incluye una serie de editores de propiedades por defecto que son suficiente para la mayora de las propiedades con las que trabajamos habitualmente. Pero como siempre, Delphi es tan potente que permite que creemos nuestros propios editores y que incluso reemplacemos los que una determinada propiedad tiene por defecto. Chupate esa VB! ;)
Definir cmo debe ser editada la propiedad. Aqu hay dos posibilidades: el valor de la propiedad puede ser modificado sobre el propio inspector de objetos (bien sea introduciendo un nuevo valor o seleccionandolo de una lista de valores), o se puede utilizar un cuadro de dilogo para dotar de mayor flexibilidad a la edicin (p.e. la propiedad color) Convertir el valor de la propiedad a un valor de tipo string. El inspector de objetos siempre trabaja con strings. Aunque la propiedad sea de tipo integer o float o sea un mtodo, el inspector de objetos slo trata con representaciones de tipo string de dichas propiedades. Es el editor de propiedades el que debe suministrar al inspector de objetos de dicha representacin en string de la propiedad. Esta "traduccin" puede ir desde lo ms sencillo (utilizar la funcin IntToStr) hasta lo ms complejo (programar una rutina para la traduccin). Todo depender del tipo de propiedad con la que estemos tratando.
Crear una nueva unidad en la que definiremos el editor de propiedades. Ms adelante hablaremos ms extensamente sobre este punto, ya que no es tan trivial como puede parecer en principio ;) Aadir la unidad DsgnIntf a la clausula uses del editor de propiedades. En esta unidad estan definidos los editores de propiedades por defecto que utiliza Delphi, adems de la importantsima clase TPropertyEditor, la cul es la clase base de todos los editores de propiedades. Crear una nueva clase que descienda de TPropertyEditor o de alguno de sus desciendentes. Por convencin, el nombre de los editores de propiedades finaliza con la palabra Property. P.e. TIntegerProperty, TStringProperty... A
Manual de Creacin de Componentes en Delphi MBS para el LTIASI continuacin se muestran los principales editores de propiedades por defecto que incorpora Delphi.
Editor de propiedades Tipo TPropertyEditor TIntegerProperty TCharProperty TEnumProperty TSetProperty TFloatProperty TStringProperty TClassProperty TMethodProperty TComponentProperty
Clase base para todos los editores de propiedades Byte, word, integer, Longint Char Tipos enumerados Sets Single, Double, Extended, Comp, Currency Strings Cualquier objeto Cualquier mtodo (eventos) Para propiedades que hacen referencia a componentes
Implementar los mtodos necesarios para dotar al editor de propiedades de las funcionalidades deseadas. Registrar el editor de propiedades en la VCL
Ms adelante profundizaremos en todos aspectos segn vayamos desarrollando distintos editores, pero ahora ha llegado del momento de crear nuestro primer editor de propiedades.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI queremos. Este es el tpico caso en el que desarrollando un simple editor de propiedades ahorramos trabajo al futuro usuario de nuestro componente, evitando que tenga que hacer la conversin entre decimal y binario. Como ya hemos comentado, tenemos que decidir de que clase vamos a derivar nuestro editor. En nuestro caso es fcil, ya que la propiedad almacenar un valor de tipo integer, as que lo adivinais? Si!, de TIntegerProperty ;) Muy bien, nuestra propiedad almacena un entero, pero no queremos que el inspector de objetos nos muestre su valor decimal directamente, queremos efectuar una conversin de dicho valor decimal a base binaria y que slo entonces el inspector de objetos nos muestre el valor. Para lograr esto debemos implementar (override) la funcin GetValue . Esta funcin, definida en TPropertyEditor, es llamada por el inspector de objetos para obtener la representacin del valor de la propiedad en forma de string. Dentro de esta funcin debemos convertir el valor de la propiedad de decimal a binario y, a continuacin, transformar este valor en string, ya que como ya hemos mencionado el inspector de objetos siempre muestra strings. De este modo la funcin GetValue queda as:
unit BinaryPropEd; interface uses DsgnIntf; type TBinIntegerProperty = class(TIntegerProperty) public function GetValue : string; override; procedure SetValue(const Value : String); override; end; procedure Register; implementation Const Bits16 : Array [1..16] of Integer = (32768,16384,8192,4096,2048,1024,512,256,128,64,32,16,8,4,2,1); function TBinIntegerProperty.GetValue : string; Var Num, i : integer; begin Num:=GetOrdValue; Result := '0000000000000000'; for i := 1 to 16 Do if ( Num >= Bits16[i] ) Then begin Num := Num - Bits16[i]; Result[i] := '1'; end; if ( Num > 0 ) Then raise EPropertyError.Create('Error converting '+IntToStr(GetOrdValue) + ' to binary'); Insert('B',Result,1); end;
Necesitamos obtener el valor que tiene en ese momento la propiedad para trabajar sobre l. Para ello utilizamos el mtodo GetOrdValue , definido de nuevo en TPropertyEditor, el cul se encarga de devolver el valor de la propiedad en forma de ordinal (integer). De forma anloga, existen los mtodos GetFloatValue, GetMethodValue, GetVarValue, etc. para utilizar con el tipo de propiedad correspondiente. Una vez almacenado el valor de la propiedad en la variable Num, comienza la conversin del valor de decimal a binario, la cal es fcil de entender. Cabe hacer notar que al mximo nmero de digitos binarios soportados es 16, margen ms que suficiente para la mayora de aplicaciones. Por ltimo tenemos que devolver un valor de tipo string como resultado de la funcin. Para ello vamos almacenando en la variable Result el string a devolver. Para finalizar, anteponemos la letra 'B' a la cadena para indicar que se trata de base binaria. Y eso es todo en lo que a esta funcin respecta. De este modo, cuando el inspector de objetos deba mostrar el valor de la propiedad, llamar a el mtodo GetValue el cul le devolver el string correspondiente. Pero nos queda la otra mitad: estara bien que pudieramas introducir el valor de la propiedad tanto en decimal como en binario segn nos interesase, verdad? pues vamos a ello. ;) Para conseguir esta funcionalidad debemos implementar (override) el mtodo SetValue , definido de nuevo en la clase TPropertyEditor. Cuando el usuario entra un nuevo valor usando el inspector de objetos, este llama al mtodo SetValue, el cul debe efectuar la traduccin inversa a la efectuada por el mtodo GetValue. Es decir, debe convertir el string que contiene el nuevo valor de la propiedad al tipo de datos de dicha propiedad. En nuestro caso, el string vendr en base decimal o binaria (en este ltimo caso, la primera letra de la cadena ser una 'B') y convertirlo a base decimal. Para ello implementaremos el mtodo SetValue de la siguiente forma:
... type TBinIntegerProperty = class(TIntegerProperty) public function GetValue : string; override; procedure SetValue(const Value : String); override; end; procedure Register; implementation ...
En la implementacin de este mtodo primero comprobamos si el usuario ha introducido el nuevo valor de la propiedad en base decimal o en base binaria. En el primer caso, tan slo hay que convertir el string a integer mediante la funcin StrToInt(Value) y, a continuacin, utilizar el mtodo SetOrdValue para almacenar el valor correspondiente. De forma anloga, segn el tipo de la propiedad, existen los mtodos SeFloatValue, etc. En el caso de que la primera letra de la cadena sea una 'B', se convierte la cadena con el valor binario a base decimal y se vuelve a utilizar el mtodo SetOrdValue para almacenar el valor en la propiedad. Una vez implementados estos dos mtodos (GetValue y SetValue) ya tenemos nuestro editor de propiedades terminado; tan slo nos queda un pequeo, pero indispensable paso, registrarlo en la VCL.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI PropertyType hace referencia al tipo de la propiedad al que se aplicar el editor de propiedades. Para suministrar un valor a este parmetro normalmente utilizaremos la funcin TypeInfo, por ejemplo, TypeInfo(integer). ComponentClass permite restringir el uso del editor de propiedades a la clase especfica. Un valor de nil registra el editor para todos los componentes. PropertyName especifica el nombre de la propiedad. Un valor distinto de nil registra el editor slo para la propiedad especificada, mientras que un valor '' lo registra para todas las propiedades EditorClass especifica el editor de propiedades que se registra (la clase). Jugando con estos parmetros, tenemos a nuestra disposicin un amplio abnico de posibilidades para registrar nuestro editor de propiedades. Vemos algunos ejemplos con nuestro recin creado editor:
RegisterPropertyEditor(TypeInfo(integer), nil, '', TBinIntegerProperty) registra el editor de propiedades para todos los componentes que tengan una propiedad de tipo entero. Est es la forma ms global de registrar un componente y afecta a todos los componentes registrados en la VCL. Si registramos nuestro editor as, veremos que todas las propiedades de tipo integer nos aperecen en binario! Adems podemos introducir un nuevo valor en decimal o en binario (anteponiendo la letra B). Estamos sustituyendo el editor de propiedades que Delphi utiliza para enteros por el nuestro! :) Esta es la forma ms global de registrar un editor de propiedades. RegisterPropertyEditor(TypeInfo(integer),TMiComponente,'PropiedadBinaria',T BinIntegerProperty) registra el editor slo y exclusivamente para la propiedad 'PropiedadBinaria' del componente 'TMiComponente'. Esta es la form ms restringida de registrar un editor de propiedades.
Os aconsejo que registreis el editor de la forma ms global posible para que experimenteis con l. Luego, una vez os canseis de ver todas las propiedades enteras en binario, os permito que le desinstaleis ;) Tambin podis crearos un componente de "mentirijillas" y registrar slo el editor para el mismo. Algo as:
... Type TMiComponente = class(TComponent) ... property PropiedadBinaria : integer read FPropBin write FPropBin; ... end; ...
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Como ya mencionamos al mostrar los pasos que se deben seguir para crear un editor de propiedades, el primero de ellos es crear una unidad donde situar el editor. En ese momento dijimos que no era una eleccin tan trivial como pudiera parecer en un pricincipio. En que unidad debemos situar el editor? en la misma unidad donde est el componente que tiene la propiedad que ser editada?, en una unidad aparte? Tenemos tres posibles ubicaciones:
La primera opcin es situar el editor de propiedades en la misma unidad en que reside el componente que tiene la propiedad que utiliza el editor de propiedades. Esta es la opcin ms intuitiva; sin embargo no es la ms recomendable, sobre todo si el editor utiliza un cuadro de dilogo. El motivo es que Delphi slo utiliza el editor de propiedades en tiempo de diseo. De hecho, el form que contiene el cuadro de dilogo no es enlazado con la aplicacin, ni tampoco la clase del editor de propiedades. Sin embargo, si se enlazan los recursos asociados al cuadro de dilogo, recursos que en tiempo de ejecucin lo nico que hacen es ocupar espacio, ya que no se utilizarn para nada. Por tanto, estamos incrementando el tamo del ejecutable a lo tonto :( Por otra parte, si el editor de propiedades no utiliza cuadros de dilogo, el efecto no es tan pernicioso pero sigue sin ser recomendable.
La segunda opcin es situar el editor de propiedades (si utiliza un form cmo cuadro de dilogo) en la misma unidad del cuadro de dilogo. De este modo, la aplicacin que usa el componente no enlaza el editor de propiedades ni sus recursos asociados. La tercera opcin es situar el editor de propiedades en una unidad de registro. Una unidad de registro es una unidad normal y corriente que agrupa varias sentencias de registro correspondientes a distintos componentes y editores de propiedades que residen en distintas unidades. Esta es la opcin ms recomendable si el editor de propiedades es utilizado por varios componentes.
Por tanto, las dos ltimas opciones son las ms recomendables dependiendo la eleccin de una u otra del uso que se vaya a dar al editor de propiedades. De todas formas, no olvideis aadir a la clausula uses las unidades correspondientes segn donde situeis el editor. En prximas unidades veremos ejemplos de distintas ubicaciones de los editores de propiedades que iremos construyendo.
Introduccin
En la anterior unidad aprendimos el funcionamiento bsico de un editor de propiedades y desarrollamos un ejemplo de un editor de propiedades que trabajaba sobre el inspector de objetos (BinaryPropEd). En esta unidad desarrollaremos cuatro! editores de propiedades y un componente que nos servira para probar los editores.
TFicheroProperty es un editor de propiedades de tipo cuadro de dilogo. En alguna ocasin desarrollaremos un componente en el que una de sus propiedades debe almacenar el nombre de un fichero. Obligaremos al sufrido usuario de nuestro componente a tener que escribir la ruta de acceso y el nombre del fichero correspondiente? Claro que no! Mediante el editor de propiedades TFicheroProperty mostraremos al usuario el tpico cuadro de dilogo OpenDialog para que le sea ms sencilla la asignacin de un determinado fichero: No olvidemos que una de las tareas principales de un editor de propiedades es facilitar la vida a los usuarios de nuestros componentes.
TAliasProperty es un editor de propiedades de tipo lista de valores y su funcionamiento es idntico al de la propiedad DatabaseName del componente TTable: permite la eleccin de un alias de base de datos mediante una lista que se despliega en el propio inspector de objetos.
Profundizando un poco ms en el tema de los editores de propiedades tipo cuadro de dilogo crearemos uno partiendo de cero, es decir, sin utilizar un cuadro de dilogo predefinido como en el caso del editor TFicheroProperty. Concretamente, crearemos un editor que nos permita la introduccin de contraseas. Imaginemos que en una propiedad de un componente necesitamos guardar un valor de tipo string (contrasea). Si no creamos ningn editor de propiedades, el usuario del componente escribir directamente el valor sobre el inspector de objetos, pero lo que escriba ser totalmente visible (no aparecern asteriscos como ocurre con el componente TEdit). Poco elegante, verdad? As que crearemos un cuadro de dilogo que nos permitir escribir y verificar la contrasea a asignar a la propiedad. Este ser nuestro editor TPasswordProperty.
Por ltimo, y como respuesta a peticiones de algunos de vosotros, crearemos un editor de propiedades, TDateTimeProperty,para la introduccin de fechas. De este modo podremos introducir cadenas con la fecha en una propiedad de tipo TDateTime (recordemos que las fechas son de tipo Float) en vez de tener que introducir el nmero equivalente.
Un componente de prueba
Antes de comenzar a desarrollar los editores de propiedades vamos a crear un componente que nos permitir probarlos segn los completemos. Este componente lo he denominado TPrueba y su cdigo es el siguiente:
unit Unidad9; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DsgnIntf, DB, PasswordForm; type TPrueba = class(TComponent) private
Nada del otro mundo. Cuatro propiedades para cada uno de los editores de propiedades a probar y un constructor de los ms normal para asignar un valor por defecto a la propiedad fecha. Lo que si conviene hacer notar es que por simplicidad desarrollaremos el componente y los cuatro editores de propiedades en una nica unidad (hay una unidad adicional necesaria para el editor TPasswordProperty). Como digo, lo haremos as por simplicidad, pero no es lo ms correcto. En general, cada editor de propiedades debe ir en una unidad independiente del propio componente, cmo ya vimos en la unidad 8.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI continuacin debemos decidir si la propiedad ser editable en el propio editor de objetos o utilizar un cuadro de dilogo. En este caso parece bastante claro que debemos elegir la segunda opcin. Por supuesto esto no quita que el usuario pueda escribir directamente el valor de la propiedad en el inspector de objetos y es que masoquistas los hay en todas partes! ;) Ahora bien, cmo indicarle a Delphi que se trata de un editor tipo cuadro de dilogo? Para lograrlo, debemos utilizar otro mtodo de la clase base de todos los editores de propiedades (TPropertyEditor): el mtodo GetAttributes Este mtodo es una funcin que determina que caractersticas tendr el editor de propiedades. Las caractersticas deseadas debemos devolverlas como resultado de esta funcin; este resultado es del tipo TPropertyAttributes El tipo TPropertyAttributes es un conjunto (set) que puede tomar los siguientes valores: Atributo paValueList paSortList Descripcin Especifica que el editor debe mostrar una lista con los valores posibles para la propiedad. Para rellenar la lista se utiliza el mtodo GetValues Slo vlida si se selecciona paValueList. Especifica que la lista de valores se mostrar ordenada.
Indica que el editor define subpropiedades a mostrar identadas a la paSubProperties derecha (p.e. la propiedad font del componente TForm). Para generar la lista de propiedades se utiliza el mtodo GetProperties. paDialog Indica que el editor de propiedades debe mostrar un cuadro de dilogo en respuesta al mtodo Edit. (p.e. la propiedad glyph de un TSpeddButton). De este modo al seleccionar la propiedad aparecer un botn con la cadena '...' para editar la propiedad. Si se selecciona este atributo, la propiedad se mostrar cuando se seleccionen multiples componentes. Si se selecciona, el inspector de objetos llama al mtodo SetValue cada vez que se cambia el valor de la propiedad. Si no se selecciona, slo se llama al mtodo SetValue cuando el usuario presiona Enter o sale de la propiedad Si se selecciona, el usuario no puede modificar el valor de la propiedad Especifica si la propiedad puede recuperar su valor original
paMultiSelect
A la vista de esta tabla, resulta fcil implementar el mtodo GetAtributes en nuestro editor de propiedades:
interface ... TFicheroProperty = class(TStringProperty) public function GetAttributes : TPropertyAttributes; override; procedure Edit; override; end; ... function TFicheroProperty.GetAttributes : TPropertyAttributes; begin
De nuevo en aras de la simplicidad slo activamos el atributo paDialog pero, por supuesto, podamos activar otros atributos tales como paMultiSelect, etc. En este caso, devolveramos [paDialog, paMultiSelect] Slo nos queda por saber cuando activar Delphi el cuadro de dilogo. Al haber seleccionado paDialog, cuando el usuario haga click en el botn '...' o haga doble click sobre la propiedad, Delphi invocar el mtodo Edit de nuestro editor de propiedades. En este mtodo debemos codificar lo necesario para mostrar el cuadro de apertura de ficheros y asignar el fichero seleccionado a la propiedad si el usuario pulsa OK. Dicho mtodo, que debemos reimplementar (override, ver la seccin de interface), queda as:
... procedure TFicheroProperty.Edit; var OpenDialog : TOpenDialog; {TOpenDialog est en la unidad Dialogs, no ovidar aadir a la clausula uses} begin OpenDialog:=TOpenDialog.Create(Application); {Creamos el cuadro de dilogo} try OpenDialog.Filter:='All files|*.*'; {Asignamos sus propiedades iniciales} if OpenDialog.Execute then {Si el usuario pulsa OK...} SetStrValue(OpenDialog.FileName); {...asignamos el nuevo valor a la propiedad} finally OpenDialog.Free; {Liberamos el cuadro de dilogo} end; end; ...
Ms fcil imposible. Tan slo comentar que nos aseguramos de la liberacin de recursos (en este caso del cuadro de dilogo) mediente el uso de la construccin try..finally Ya est! Con slo 10 o 15 lneas de cdigo hemos aliviado el sufrimiento del pobre usuario que se dejaba los dedos escribiendo nombres de ficheros. Si es que somos unos santos! ;) Slo nos queda registrarlo con la sentencia:
procedure Register; begin ... RegisterPropertyEditor(TypeInfo(string),TPrueba,'Fichero',TFicheroProp erty); ... end;
Manual de Creacin de Componentes en Delphi MBS para el LTIASI especifica el alias de la base de datos al que est conectada la tabla. Para seleccionar un valor para esta propiedad existe una lista desplegable que le muestra al usuario todos los alias disponibles. Este es el comportamiento que queremos para nuestra propiedad Alias. Podramos buscar por el cdigo fuente de la VCL y tratar de registrar el editor de propiedades de la propiedad DatabaseName para que incluya tambin nuestra nueva propiedad, pero cmo es muy sencillo la construccin de un editor as, vamos a desarrollarlo nosotros mismos. La seccin de interface de nuestro editor es la asiguiente:
... TAliasProperty = class (TStringProperty) public function GetAttributes : TPropertyAttributes; override; procedure GetValues(Proc : TGetStrProc); override; end; ...
El mtodo GetAttributes lo hemos conocido en la seccin anterior. Nos basta con devolver los valores paValueList para indicar que el editor de propiedad ser editable desde el propio inspector de objetos en forma de lista de valores y paSortList para que nos muestre los diversos alias existentes ordenados alfabeticamente.
function TAliasProperty.GetAttributes : TPropertyAttributes; begin Result:=[paValueList, paSortList]; end;
La tarea que nos queda ahora es llenar la lista con los diversos alias disponibles. Para ello, reimplementaremos (override) el mtodo GetValues. Este mtodo recibe un nico parmetro: un puntero a mtodo. Realmente este puntero referencia el mtodo interno Add para el string list interno utilizado para llenar la lista desplegable. Los diversos elementos se aaden a la lista en el mtodo GetValues invocando el mtodo referenciado por el puntero al mtodo y convirtiendolo en un valor tipo string. Suena complicado, verdad? No os preocupeis, que no lo es tanto, tan slo significa que hay que aadir una sentencia del tipo Proc(valor string) por cada elemento a aadir a la lista. En nuestro caso, como queremos aadir los nombres de los alias existentes, haremos un bucle que har una llamada a Proc(nombre de alias) para ir aadiendo todos los valores. Previamente, habremos obtenido los alias existentes mediante el mtodo GetAliasList del objeto TSession:
procedure TAliasProperty.GetValues(Proc : TGetStrProc); Var AliasList : TStringList; {lista con los alias existentes} i : integer; begin try AliasList := TStringList.Create; {Creamos la lista} Session.GetAliasNames(AliasList); {Obtenemos los alias existentes} for i:=0 to AliasList.Count - 1 do {Por cada alias...} Proc(AliasList[i]); {...hacemos la llamada al mtodo Proc} finally AliasList.Free; {Liberamos la lista} end;
Con esto ya hemos construido nuestro nuevo editor de propiedades. Ya slo nos falta registrarlo:
procedure Register; begin ... RegisterPropertyEditor(TypeInfo(String),TPrueba,'Alias',TAliasProperty ); ... end;
Lo ms importante es asignar a la propiedad PasswordChar de los dos TEdit, denominados PW1 y PW2, el caracter '*' para que dicho caracter aparezca cuando un usuario teclea una contrasea. Adems, en respuesta al evento OnCloseQuery comprobaremos la validez de la contrasea introducida. El cdigo de la unidad PasswordForm queda as:
unit PasswordForm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons; type TfrmPassword = class(TForm) lpwd: TLabel;
Ya hemos construido el cuadro de dilogo, ahora slo nos resta "engancharlo" al editor de propiedades. Para ello, efectuamos la llamada al form en el mtodo Edit del editor de propiedades. Veamos como queda el cdigo del editor:
function TPasswordProperty.GetAttributes : TPropertyAttributes; begin Result:=[paDialog]; end; function TPasswordProperty.GetValue : string; begin Result:=Format('(%s)',[GetPropType^.Name]); end; procedure TPasswordProperty.Edit; begin frmPassword := TfrmPassword.Create(Application); try frmPassword.Caption:=GetComponent(0).Owner.Name+'.'+ GetComponent(0).Name+'.'+GetName+' - '+ frmPassword.Caption; frmPassword.PW1.Text:=GetStrValue; frmPassword.PW2.Text:=frmPassword.PW1.Text; if frmPassword.ShowModal = mrOK then SetStrValue(frmPassword.PW1.Text)
Slo hay una cosa nueva : en el mtodo GetValue no queremos que se nos muestre el valor de la contrasea, ya que entonces para que nos hemos tomado tantas molestias? De modo que debemos mostrar otra cosa. Podra ser una cadena de asteriscos, pero un convenio utilizado en estos casos es mostrar el tipo de la propiedad, en este caso un string. Dicho esto, slo nos queda registrar nuestro tercer editor de propiedades:
procedure Register; begin ... RegisterPropertyEditor(TypeInfo(String),TPrueba,'Password',TPasswordPr operty); end;
Nada nuevo aqu. Tan slo nos limitamos a llamar a los mtodos GetFloatValue y SetFloatValue segn nos interesa
Conclusiones
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Con esto damos por terminado el tema de los editores de propiedades. Hemos aprendido a crear editores editables sobre el inspector de objetos, bien sea directamente o mediante una lista desplegable, as como editores de tipo cuadro de dilogo. La potencia de un editor de propiedades es inmensa: mediante los metodos SetValue y GetValue podemos efectuar una verificacin del valor de la propiedad y actuar en consecuencia. Adems, nuestro editor puede hacer mil cosas: consultar un fichero ini, efectuar complejos clculos o incluso operar con una base de datos! Pero los editores de propiedades tienen dos limitaciones:
Operan nicamente en tiempo de diseo, aspecto que debemos tener en cuenta para que el usuario no se quede "colgado" al intentar operar con una propiedad en tiempo de ejecucin. Un ejemplo claro son las propiedades de tipo List, p.e. la propiedad items de un TListBox. En tiempo de diseo un editor de propiedades especifico permite al usuario de forma intuitiva aadir y eliminar elementos. A su vez, en tiempo de ejecucin, se dispone de los mtodos Add y Delete para operar con la lista. Un editor de propiedades opera nicamente con el valor de una propiedad. Es decir, no podemos alterar el valor de distintas propiedades a la vez, ya que los mtodos GetValue y SetValue slo hacen referencia a la propiedad que esta siendo editada, y no al resto de ellas. Por tanto, cuando queramos alterar varias propiedades de un componente al mismo tiempo, deberemos utilizar un Editor de componentes, tema este que ser objeto de nuestra prxima unidad. Para que se os vaya haciendo la boca agua de lo que se pude lograr con un editor de componentes, os dire que el editor de campos que aparece al hacer doble click en un objeto TTable, o el editor de mens son algunos ejemplos de editores de propiedades... :)
function TAliasProperty.GetAttributes : TPropertyAttributes; begin Result:=[paValueList, paSortList]; end; procedure TAliasProperty.GetValues(Proc : TGetStrProc); Var
unit PasswordForm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons; type TfrmPassword = class(TForm) lpwd: TLabel; lVpwd: TLabel; PW1: TEdit; PW2: TEdit; bOK: TBitBtn; bCancel: TBitBtn; procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); private public end; var frmPassword: TfrmPassword; implementation {$R *.DFM} procedure TfrmPassword.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if (ModalResult=mrOK) then if (PW1.Text = '') then begin ShowMessage('Debe introducir una contrasea'); CanClose:=False; end else if (ModalResult=mrOK) and (PW1.Text <> PW2.Text) then begin ShowMessage('Verificacin fallida. Por favor reintente'); CanClose:=False; end; end; end.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Si, si ya lo s. No creais que lo he olvidado. Habamos quedado que la unidad 10 tratara del tema de los editores de componentes. Lo que pasa es que para explicar este tema... necesitamos un componente apropiado! De modo que esta unidad tratar sobre un nuevo componente sobre el cual explicaremos el tema de los editores de componentes en la unidad 11. Adems, as matamos dos pjaros de un slo tiro. Me explico: ltimamente en el grupo de mensajes de Delphi han aparecido varias consultas sobre como evitar el parpadeo que se produce al dibujar imgenes que cambian frecuentemente. Este parpadeo (flicker) se produce al dibujar directamente las imganes en la pantalla. La solucin es sencilla: basta con dibujar previamente la imagen en un bitmap oculto (off-screen) y cuando el dibujo este finalizado, volcarlo a la pantalla. De este modo se evita el parpadeo aunque estemos dibujando el grfico muchas veces por segundo. Vamos a aplicar esta tcnica al componente TDigitalDisplay. Se trata de un display digital tipo calculadora.
Ser escalable. Es decir, ser independiente de la resolucin, De este modo tendr una buena apariencia sea cul sea el tamao del componente. Este es un aspecto fundamental al hacer un componente grfico. En este componente en concreto es fundamental que este libre de parpadeos, ya que uno de sus usos puede ser de cronmetro, lo cul implica redibujar el componente una vez por segundo (o incluso una vez por centsima de segundo!). Queda claro que no podemos permitirnos ningn tipo de parpadeo, verdad? Ser fcilmente configurable. Y al decir esto me esto refiriendo a dos aspectos: Por una lado le dotaremos de las suficientes propiedades y eventos para que el usurio pueda configurarlo a su gusto. Por otro lado, este es el tpico componente del que luego van a descender otros: el display de una calculadora, el de un lector de discos compactos... Por tanto tenemos que disearlo juiciosamente, de forma que un futuro desarrollador pueda elegir lo que le interesa y no le interesa del componente bsico.
Antes de ponermos manos a la obra os adelanto que es lo ms interesante de este componente y, por tanto, a qu debeis poner ms atencin ;):
El manejo de bitmaps ocultos (off-screen) La creacin de mtodos dinmicos que luego un futuro desarrollador pueda reimplementar (override) El trabajo con el objeto Canvas.
Consideraciones generales
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Comenzemos a disear nuestro componente. Nuestro display constar de un nmero de dgitos configurable por el usuario, por lo que necesitamos una propiedad Digits que se encargue de este aspecto. Cada dgito estar formado por siete segmentos (volveremos sobre esto en la siguiente seccin). Cada segmento puede estar encendido (propiedad DigitOnColor) o apagado (propiedad DigitOffColor). Otra propiedad ser el color de fondo (propiedad BckgndColor). Por supuesto, necesitaremos almacenar el valor a representar (propiedad Value ) y, adems, le dotaremos de alineacin (propiedad Alignment), de la posibilidad de rellenar con ceros a la izquierda (propiedad LeadingZeros ) y de estar o no visible (adivinais? propiedad Visible ). Por ltimo le dotaremos de unos cuantos eventos estndar: OnClick, OnDragOver, OnDragDrop, OnMouseUp... y de un evento personalizado: OnDigitClick, que se disparar cuando se haga click sobre un dgito (si se hace click sobre parte del componente que no sea un dgito se disparar el evento OnClick). Nuestro componente descender de TGraphicControl y la trea de dibujado del mismo se llevar a cabo en el mtodo Paint , el cul habr que redefinir (override). Por cierto es "redefinir" la traduccin adecuada de "override"? No estoy seguro, as que, por favor, sacarme de dudas :( Con esto ya tenemos el marco en el que desarrollaremos el componente, asi que manos a la obra!
El problema principal al dibujar el componente es que nuestro componente tendr un ancho y alto definidos por el usuario, y estos valores pueden oscilar en un amplio rango de valores. As el usuario puede querer un display de 100x50 (pequeo) o uno de 600x300 (enorme). Y el componente tiene que tener una buena "apariencia" en ambos casos. Cmo lograrlo? Tenemos dos opciones: 1. Crear un bitmap para cada uno de los dgitos (0..9, . y :) y guardarlo en un fichero de recursos. Despus en el mtodo Paint ajustar el tamao de cada dgito al tamao real del componente mediante el mtodo StretchDraw. Este es el mtodo ms sencillo pero el que da peores resultados, ya que si por ejemplo dibujamos los digitos con un tamao de 50x100, estos se vern muy ml a tamaos inferiores y a tamaos muy superiores. 2. Dibujarlos a escala. Para cada segmento, guardamos en un array las coordenadas de sus vrtices y luego, a la hora de dibujarlo multiplicamos estas coordenadas
Manual de Creacin de Componentes en Delphi MBS para el LTIASI por la escala correspondiente. As, al unir los vrtices mediante lneas, la apariencia ser perfecta. Este es el mtodo ms costoso, pero es el que ofrece mejores resultados y ser el que adoptemos. Si nos fijamos en los segmentos, veremos que los hay de 4,5 y 6 vrtices. Por simplicidad haremos que todos tengan 6 vrtices para almacenarlos en una matriz de 6 elementos. A los segmentos que les falten vrtices, simplemente se los duplicaremos. Se ha tomado como origen el punto 0,0 y el punto ms lejano del origen (el vrtice inferior derecho del segmento 3) tiene como coordenadas (26,32), es decir, he dibujado los segmentos sobre una plantilla de 26x34 pixels. Este tamao es arbitrario, ya que luego cada vrtice se le multiplicar por la escala correspondiente. De este modo he definido dos matrices: una para las coordenadass x y otra para las coordenadas y. Por supuesto se podra haber hecho con una sla matriz, pero como hay que aadirle otra dimensin a la matriz (el indice del segmento) queda un poco ms claro as.
Const {Coordenadas de los puntos} {Coordenadas de los 10 (1..10) segmentos posibles: 7 segmentos + 1 punto decimal (.), 2 puntos de hora (:) Cada segmento tiene 6 (0..5) vrtices} { _ } { . |_| } { : |_| } Px : Array[1..10,0..5] of integer = ((9,9,24,24,20,13),(26,26,26,24,22,22),(26,26,26,22,22,24), (24,24,9,9,13,20),(7,7,7,9,11,11),(7,7,7,11,11,9), (11,13,20,22,20,13),(2,2,5,5,5,2),(2,2,5,5,5,2),(2,2,5,5,5,2)) ; Py : Array[1..10,0..5] of integer = ((0,0,0,0,4,4),(2,2,14,16,14,6),(20,20,32,28,20,18), (34,34,34,34,30,30),(32,32,20,18,20,28),(14,14,2,6,14,16), (17,15,15,17,19,19),(32,32,32,32,34,34),(22,22,22,22,25,25),(10,10,10, 10,13,13)); {Para cada digito del 1 al 9, 1 representa segmento encendido, 0 apagado} DigitsArray : Array[0..9] of string = ('1111110','0110000','1101101','1111001','0110011','1011011', '1011111','1110000','1111111','1111011'); Seg_dot = 8; {Segmento correspondiente al .} Seg_DotDown = 9; {Segmento correspondiente al punto inferior de :} Seg_DotUp = 10; {Segmento correspondiente al punto superior de :}
Hemos definido las dos matrices constantes px y py con las coordenadas de los vrtices. Adems, se define la matriz DigitsArray de 10 elementos (0..9) que contiene que segmentos estn encendidos y apagados para cada dgito. Por ejemplo, el dgito 1 tiene encendidos los segmentos 2y 3 y apagados el resto. Aqu no se tienen en cuenta los segmentos extras (. y :). Por ltimo a los segmentos correspondientes a el punto decimal y a los puntos de la hora se les asigna un nmero y una constante para luego poder referirse a ellos con mayor facilidad.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Ya tenemos las bases necesarias para poder pintar el display en el mtodo Paint. Eso sin olvidar que debemos eliminar el parpadeo...
En el mtodo Create aadimos a la propiedad ControlStyle el atributo csOpaque: ControlStyle:=ControlStyle+[csOpaque]; Esta propiedad esta definida en el objeto TControl e indica los diferentes estilos que se aplican al componente (ver la ayuda en lnea para ms informacin). Cuando queremos que nuestro componente se repinte, (por ejemplo en los mtodos Set de las propiedades) llamamos al mtodo Repaint y no a Invalidate. Repaint no borra el area del componente antes de redibujarlo de nuevo (Invalidate si lo hace). Para que repaint nos funcione adecuadamente es por lo que necesitamos que nuestro componente sea opaco (estilo csOpaque).
Ahora si, nuestro componente esta libre de parpadeos al 100% (bueno, quizs exagere un poco y sea realmente al 99%) :) Y si no me creeis, hacer la prueba con el programa mostrado al final de la unidad Incrdulos! ;) Bien, una vez conocida la teora, es cuestin de aplicarla. Este es el mtodo Paint de nuestro componente:
procedure TDigitalDisplay.Paint; Var ex, ey : single; {Escala a la que dibujar el display}
Como se puede ver el mtodo Paint utiliza una serie de rutinas auxiliares para el dibujado del display. Estos mtodos son:
GetNumberOfDigits , que se encarga de devolver el nmero de caracteres numricos que hay en la propiedad Value. Por ejemplo, si la propiedad contiene el valor 12:25, devolver 4. DrawDot es un mtodo que se encarga de dibujar los puntos de separacin horaria y decimal. DrawDigit, que se encarga de dibujar un dgito en la posicin correspondiente del Canvas.
Creo que no hace falta explicar cmo se procede al dibujado de cada dgito en el canvas, ya que las funciones utilizadas para ello son de todos bien conocidas (Canvas.Pen, Canvas.Brush y Canvas.Polygon). En cualquier caso si os surgen dudas no teneis ms que escribirme :) Vamos ahora con el ltimo aspecto interesante del componente: hacer que otros desarrolladores puedan descender sus propios componentes de nuestro Display original
Manual de Creacin de Componentes en Delphi MBS para el LTIASI procedure Paint; override; Que, dicho sea de paso, es lo mismo que hemos tenido que hacer nosotros :) Y es que no olvidemos que nuestro componente, a su vez, es hijo de TGraphicControl y gracias a Dios! en dicho componente el mtodo Paint se declaro protected y no private ;) Nos queda por implementar un evento, OnDigitClick que se debe disparar cuando el usuario del componente haga click sobre un digito del display. Hay que distinguir entre el click sobre un dgito (que disparar el evento OnDigitClick ) y el click sobre el resto del display (que disparar el evento OnClick ). Lo primero es declarar un campo FOnDigitClick para el nuevo evento. Adems declararemos el tipo de procedimiento del evento: TOnDigitClick = procedure(Sender : TObject; Digit : integer) of object; Para disparar este evento, redefiniremos el procedimiento MouseDown. Para ello declararemos el mtodo en la parte protected (ya sabeis, para que los hijos...) y aadiremos la palabra override. Esta es la implementacin de dicho mtodo:
procedure TDigitalDisplay.MouseDown(Button : TMouseButton; Shift : TShiftState; X,Y : Integer); {Mtodo que se encarga de determinar si se ha pulsado con el ratn sobre un dgito del display. En caso afirmativo se dispara el evento correspondiente} var i, xt, xoff, yoff, DigitSpace : integer; begin inherited MouseDown(Button,Shift,X,Y); {Clculo del offset horizontal} xoff:=Trunc(0.2*width); xt:=Trunc(0.2*width) div 2; {Clculo del espacio ocupado por un dgito} DigitSpace:=Trunc((Width-xoff) / FDigits); {Vamos comprobando si el click se ha ha hecho sobre el dgito (i)} for i:=1 to FDigits do begin if (x>=xt) AND (x<=xt+DigitSpace) then DigitClick(i); xt:=xt+DigitSpace; end; end;
Lo primero que hace el mtodo es llamar al antecesor del mismo mediente la llamada inherited. A continuacin se calcula si se ha hecho click sobre un dgito. En caso afirmativo se ejecuta el procedimiento DigitClick pasndole como parmetro el nmero de dgito sobre el que se ha hecho click. El mtodo DigitClick , por su parte, se encarga de disparar el evento anteriormente definido:
procedure TDigitalDisplay.DigitClick(Digit : integer); {Mtodo que dispara el evento OnDigitClick} begin if Assigned(FOnDigitClick) then FOnDigitClick(Self,Digit); end;
Y aqu esta el meollo de la cuestin. Por qu hemos definido el mtodo DigitClick y no hemos escrito las sentencias correspondientes dentro del mtodo MouseDown? La
Manual de Creacin de Componentes en Delphi MBS para el LTIASI respuesta es reusabilidad. Si un usuario del componente quiere que en respuesta a un click sobre un dgito dicho dgito cambie, codificara las sentencias necesarias en respuesta al evento OnDigitClick. Pero si es un nuevo programador el que esta desarrollando un hijo de TDigitalDisplay no puede hacer esto. A este programador el procedimiento MouseDown le sirve perfectamente y tan slo tiene que reimplementar el procedimiento DigitClick y poner all lo que quiera. Si lo hubieramos codificado todo en un nico procedimiento esto no sera posible. De ah que mantengamos separados el procedimiento que disparar el evento y el disparo del evento en s. De este modo aseguramos una gran flexibilidad a futuros desarrolladores: por ejemplo, un hijo reimplementara DigitClick para incrementar el valor del dgito (por ejemplo, para ajustar la hora), mientras que otro lo reimplementara para poner el valor a cero (en un cronmetro). En ambos casos el mtodo MouseDown ser el mismo y bastar con trabajar sobre DigitClick directamente. El ltimo detalle importante es que el mtodo DigitClick se ha declarado dynamic, ya que si no se declara as, no se puede reimplementar, ya que los mtodos estticos no se pueden redefinir, slo los dinmicos (ya sean dynamic o virtual). En otras unidades profundizaremos ms sobre este tema. Mientras os refiero a la ayuda en lnea de Delphi para ms informacin sobre este tema. Con esto queda terminado el componente. Quizs haya sido una explicacin un poco breve pero considero que con el nivel que ya tenemos no debeis teneis problema para entender el funcionamiento del mismo. Os recomiendo que estudies la forma de trabajar con off-screen bitmaps, ya que es un tema que se utiliza frecuentemente en la programacin grfica bajo Windows. Y Recordar que este componente nos servira en la prxima unidad para desarrollar el tema de los editores de propiedades.
end.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Como vimos en las unidades 8 y 9, un editor de propiedades trabaja sobre la representacin en forma de string del valor de una propiedad de un componente determinado. El objetivo de esta manipulacin es permitir al usuario del componente leer y almacenar el valor de la propiedad que est siendo editada. Adicionalmente un editor de propiedades puede transformar el valor de la propiedad convirtindola a un valor ms apropiado para el funcionamiento interno del componente. Un ejemplo es el editor que construimos para las propiedades de tipo de fecha, que muestra al usuario la fecha en formato "legible" (dd/mm/aa), pero que opera internamente con un float (TDateTime). Pero la propia filosofa de diseo de los editores de propiedades trae consigo una ventaja que es a su vez una desventaja: un editor de propiedades slo conoce del componente la propiedad a editar y nada ms. Recordemos que un editor de propiedades lea y almacenaba el valor de la propiedad por medio de los mtodos GetOrdValue, SetOrdValue, GetFloatValue, etc. Estos mtodos proporcionan el nico medio de comunicacin que el editor tiene con el componente. Un editor de componentes, en cambio, puede acceder al componente directamente, ya que una de las propiedades que todo editor de componentes tiene es la propiedad Component, que devuelve una referencia al componente que est siendo editado. De este modo, el editor de componentes puede acceder a todos los mtodos y propiedades que el componente define como published o public (e incluso a los definidos como private o protected s el editor reside en la misma unidad que el componente). Por tanto, si queremos manipular una nica propiedad de un componente utilizaremos un editor de propiedades. Si, por el contrario, queremos manipular varias propiedades al mismo tiempo, o ejecutar algn mtodo del componente, deberemos utilizar un editor de componentes. Adems, los editores de componentes permiten definir nuevas acciones en forma de men tems que se aaden al men contextual que aparece al seleccionar un componente y hacer click sobre l con el botn derecho del ratn (p.e. el editor de componente TPageControlEditor permite aadir/eliminar nuevas pginas del componente TPageControl). Otra posibilidad que permiten los editores de componentes es ejecutar una accin determinada cuando el usuario hace doble click sobre un componente determinado (p.e. el editor de componentes TMenuEditor ejecuta el editor de mens del componente TMainMenu). A modo de resumen la figura siguiente muestra como se comunican los editores de propiedades y de componentes con el componente a editar:
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Todos los editores de componentes descienden de una clase base denominada TComponentEditor. La declaracin de dicha clase es la siguiente:
TComponentEditor = class private FComponent: TComponent; FDesigner: TFormDesigner; public constructor Create(AComponent: TComponent; ADesigner: TFormDesigner); virtual; procedure Edit; virtual; procedure ExecuteVerb(Index: Integer); virtual; function GetVerb(Index: Integer): string; virtual; function GetVerbCount: Integer; virtual; procedure Copy; virtual; property Component: TComponent read FComponent; property Designer: TFormDesigner read FDesigner; end;
Vamos a ver con ms detenimiento las propiedades y mtodos declarados en esta clase:
property Component . Propiedad de slo lectura. Devuelve una referencia al componente que est siendo editado. Esta propiedad es fundamental en todo editor de componentes, ya que gracias a ella podemos asignar/recuperar los valores de las distintas propiedades del componente. Conviene hacer notar que es del tipo TComponent, por lo que tendremos que hacer un casting al tipo de componente especfico. property Designer. Propiedad de slo lectura. Devuelve una referencia a Diseador de Forms de Delphi. Por lo general la utilizaremos para comunicar a Delphi que el componente ha cambiado. procedure Create. Aqu hay poco que decir, ya que este mtodo es un viejo conocido de todos nosotros. ;-) procedure GetVerbCount, GetVerb y ExecuteVerb. Cuando se selecciona un componente y se hace click sobre l con botn derecho, Delphi llama al mtodo GetVerbCount para ver si hay que aadir entradas al men contextual del componente. Este men debe devolver el nmero de entradas (tems) a aadir al mencionado men. Si este mtodo devuelve un valor distinto de cero, Delphi llamar al mtodo GetVerb tantas veces como entradas se deban aadir. Este mtodo debe devolver el string que se debe visualizar en el men por cada elemento aadido. Para ello Delphi le pasa el ndice del elemento a obtener el string (siendo el primero el que tiene el ndice 0). Por ltimo, cuando el usuario hace click sobre una entrada de men Delphi llama al mtodo ExecuteVerb, pasndole como parmetro el ndice del men tem seleccionado. En este mtodo se debe codificar cualquier accin que deba llevar a cabo el editor del componente (p.e. mostrar un form auxiliar para editar las propiedades del componente). procedure Edit. Este mtodo se invoca cuando el usuario hace doble click sobre el componente y es equivalente a la accin ExecuteVerb(0). Es decir, hacer
Manual de Creacin de Componentes en Delphi MBS para el LTIASI doble click sobre el componente implica ejecutar el primer tem del men contextual. No parece complicado, verdad? De hecho, es ms sencillo escribir editores de componentes que editores de propiedades. Y vamos a demostrarlo escribiendo nuestro primer editor de componentes.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Como se puede observar en la figura, el form consta de un componente TDigitalDisplay utilizado para mostrar los diversos cambios efectuados a las propiedades, y una serie de controles que permiten alterar el valor de dichas propiedades. La construccin de este form es muy sencilla, por lo que muestro directamente el cdigo fuente del mismo:
type TfrmDigitalDisplay = class(TForm) gbMuestra: TGroupBox; DigitalDisplay1: TDigitalDisplay; rgAlineacion: TRadioGroup; gbValor: TGroupBox; lValor: TLabel; eValor: TEdit; cbRellenarCeros: TCheckBox; eDigitos: TEdit; lDigitos: TLabel; upDigitos: TUpDown; gbColores: TGroupBox; lFondo: TLabel; lDigitosOn: TLabel; lDigitosOff: TLabel; pColorFondo: TPanel; pColorOn: TPanel; pColorOff: TPanel; bbAceptar: TBitBtn; bbCancelar: TBitBtn; ColorDialog1: TColorDialog; gbDimensiones: TGroupBox; lAncho: TLabel; eAncho: TEdit; lAlto: TLabel; eAlto: TEdit; procedure pColorFondoClick(Sender: TObject); procedure pColorOnClick(Sender: TObject); procedure pColorOffClick(Sender: TObject); procedure rgAlineacionClick(Sender: TObject); procedure eDigitosChange(Sender: TObject); procedure upDigitosClick(Sender: TObject; Button: TUDBtnType); procedure cbRellenarCerosClick(Sender: TObject); procedure eValorChange(Sender: TObject); procedure eAnchoChange(Sender: TObject); procedure eAltoChange(Sender: TObject); private public end; ... implementation ... {---------------------------------------} { Form del Editor de propiedades } {---------------------------------------} procedure TfrmDigitalDisplay.pColorFondoClick(Sender: TObject); begin ColorDialog1.Color:=pColorFondo.Color; if ColorDialog1.Execute then pColorFondo.Color:=ColorDialog1.Color; DigitalDisplay1.BckgndColor:=pColorFondo.Color;
Una vez diseado el form, vamos a crear el editor de componentes propiamente dicho.
Implementado el editor
Nuestro editor de componentes deriva directamente de TComponentEditor. La declaracin del mismo es la siguiente:
TDigitalDisplayEditor = class(TComponentEditor) function GetVerbCount : integer; override; function GetVerb(Index : integer) : string; override; procedure ExecuteVerb(Index : integer); override; procedure PrepararForm(aForm : TfrmDigitalDisplay); procedure ActualizarComponente(aForm : TfrmDigitalDisplay); end;
Por convencin, los editores de componentes finalizan con la palabra Editor (TTableEditor, TPageControlEditor, ...) Nuestro editor aadir una nica opcin al men contextual del componente. Dicha opcin se activar cuando el usuario seleccione la opcin o haga doble click sobre el componente, tal y cmo se explico anteriormente. Por tanto definimos el mtodo GetVerbCount de la siguiente manera:
function TDigitalDisplayEditor.GetVerbCount : integer; begin Result:=1; end;
Nuestro mtodo se limita a comunicar que slo se aade un tem al men contextual. A continuacin indicamos que el tem a aadir debe tener la leyenda "&Edit"
function TDigitalDisplayEditor.GetVerb(Index : integer) : string; begin Result:='E&dit...'; end;
Y ahora la parte ms interesante. El mtodo ExecuteVerb se debe encargar de las siguientes tareas: 1. Crear el form diseado en la seccin anterior. 2. Asignar a los controles del form los valores de las propiedades que actualmente tiene el componente real (entendiendo por componente real el que el usuario ha seleccionado de la paleta de componente y ha pinchado sobre el form correspondiente). 3. Mostrar, de forma modal, el form de edicin. 4. Si el usuario sale del form aceptando los cambios (OK) se debe actualizar el componente real con los valores que el usuario ha elegido en el form. 5. Debemos comunicar a Delphi que el componente ha cambiado. Este paso es muy importante . Para ello, ejecutamos la sentencia Designer.Modified 6. Liberar el form. Todos estos pasos son tpicos sea cul sea el editor de componentes que estemos desarrollando, de forma que os recomiendo que os familiaricis con ellos. Pos supuesto,
Manual de Creacin de Componentes en Delphi MBS para el LTIASI si no se necesita el form auxiliar, se obvian los pasos correspondientes ;) La secuencia mostrada queda codificada en nuestro editor en concreto de la siguiente manera:
procedure TDigitalDisplayEditor.ExecuteVerb(Index : integer); procedure CopyDigitalDisplay(Dest, Source : TDigitalDisplay); {procedimiento auxiliar que copia los valores de las propiedades de un componente digital display a otro} begin Dest.Digits:=Source.Digits; Dest.LeadingZeros:=Source.LeadingZeros; Dest.Value:=Source.Value; Dest.Alignment:=Source.Alignment; Dest.BckgndColor:=Source.BckgndColor; Dest.DigitOnColor:=Source.DigitOnColor; Dest.DigitOffColor:=Source.DigitOffColor; Dest.Width:=Source.Width; Dest.Height:=Source.Height; end; Var aForm : TfrmDigitalDisplay; begin {Creamos el form del editor de propiedades} aForm:=TfrmDigitalDisplay.Create(Application); try {Asignamos el caption correspondiente} aForm.Caption:=Component.Owner.Name+'.'+Component.Name+' '+aForm.Caption; {Asignamos a los controles del form los valores correspondientes del componente a editar} PrepararForm(aForm); {Actualizamos el DigitalDisplay utilizado como muestra para que presente el mismo aspecto que tiene el componente real} CopyDigitalDisplay(aForm.DigitalDisplay1, Component As TDigitalDisplay); {Si el usuario acepta los cambios efectuados al form} if aForm.ShowModal = mrOK then begin {Actualizamos el componente en base a los controles del form} ActualizarComponente(aForm); {Informamos a Delphi que el componente ha cambiado} Designer.Modified; end; finally {Liberamos el form} aForm.Free; end; end;
El procedimiento PrepararForm se encarga de asignar a los controles del form los valores correspondientes de las propiedades del componente a editar. Para ello utiliza el mtodo CopyDigitalDisplay. De una forma anloga, el mtodo ActualizarComponente se encarga de asignar al componente real los valores del
Manual de Creacin de Componentes en Delphi MBS para el LTIASI form auxiliar. Nota: Nos podramos haber ahorrado el mtodo ActualizarComponente y haber asignado al componente real los valores de las propiedades de los controles del form mediante el mtodo CopyDigitalDisplay. Si no lo he hecho as, es porque creo que queda ms claro tener dos mtodos: uno para asignar los valores a los controles del form y otro para asignarlos al componente real, pero es simplemente cuestin de gustos. :-)
Vuelvo a recalcar aqu que es indispensable informar a Delphi que se han actualizado las propiedades del componente mediante una llamada al mtodo Modified del objeto Designer. En caso contrario Delphi no actualizara en el inspector de objetos los cambios realizados.
El segundo aspecto a tratar es dnde situar el editor de componentes. Aqu se aplican los mismos principios que explicamos en la primera unidad en que estudiamos los editores de propiedades. Ya que los editores de componentes slo se necesitan en tiempo de diseo no se deben situar en la misma unidad en que resida el componente, sino en una unidad independiente. Lo que s se suele hacer es situarlo en la misma unidad donde reside el form auxiliar, tal y como lo hacemos nosotros con nuestro editor.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Antes de nada, una pequea disculpa por la tardanza de esta nueva unidad. El motivo no es otro que el trabajo. He estado muy ocupado estas ltimas semanas desarrollando un componente para la empresa en la que trabajo. Se trata de un conjunto de componentes que dotan a las aplicaciones desarrolladas en Delphi de un potente mecanismo de seguridad. Pero no os preocupeis que nos os voy a soltar el rollo. Tan slo os invito a bajaros la beta, totalmente funcional hasta el 15-6, de nuestra pgina web: www.dadvina.com o, si os resulta ms comodo, tan slo debis pinchar en el banner que aparece al principio de est pgina.
Introduccin
En esta unidad aprendemos a crear componentes enlazados a bases de datos. Los componentes de base de datos (data-aware components) son simplemente componentes estndar, cmo los que hemos estado creando a lo largo del curso, a los que se les aade la caracterstica de poder enlazarse con una base de datos. Este enlace puede ser de slo lectura (el componente puede reflejar el estado del campo de la base de datos, pero no actualizarlo), o de edicin (si es posible realizar esta actualizacin). El componente que desarrollaremos ser un componente de edicin descendiente de TTrackBar, el componente que incorpora Delphi en la pestaa Win95.
He optado por esta eleccin ya que ilustra el caso ms comn: a partir de un componente existente, descendemos uno nuevo enlazado a una base de datos (tal y como ocurre por ejemplo con el control TDBEdit, descendiente de TEdit). Antes de empezar conviene diferenciar entre dos tipos de componentes enlazados a datos:
Los que se enlazan a un slo campo de la base de datos, cmo por ejemplo los controles TDBEdit, TDBLabel, etc. y el componente que desarrollaremos en esta unidad Los que se enlazan a mltiples campos de la base de datos, cmo el control TDBGrid.
De modo que pongmonos manos a la obra estudiando como se realiza la conexin entre el componente y la base de datos.
Todos los componentes de bases de datos utilizan un objeto TDataLink para comunicarse con la base de datos. Pero generalmente el componente no se conecta ciertamente a este objeto, sino que lo hace a uno de sus descendientes: TFieldDataLink o TGridDataLink. Cundo utilizar uno u otro? Nada ms fcil: si el componente que queremos crear debe conectarse a un solo campo de la base de datos utilizaremos un TFieldDataLink (por ejemplo los componentes DBEdit, DBLabel,... ). Si, por el contrario, nuestro componente debe conectarse a varios campos de la base de datos utilizaremos el objeto TGridDataLink (o en algn caso, el objeto TDataLink directamente). Cmo ya hemos mencionado, nuestro componente se conectar a un slo campo de la base de datos por lo que conviene que estudiemos las propiedades y mtodos del objeto TFieldDataLink: Mtodos : Edit : al llamar a este mtodo, el registro actual del data source se pone en estado de edicin (siempre y cuando sea posible). Si tiene xito, se devuelve True, en caso contrario se devuelve False. Modified : cuando el componente de base de datos cambia los valores del control debe informar al objeto data source de que dicho campo se ha producido. Para ello se llama a este mtodo. Cabe destacar que la llamada a este mtodo es slo una notificacin al objeto de que se han producido cambios, pero nada ms. Ser el propio DataLink el que, cuando los necesite, nos pedir el nuevo valor a almacenar en la base de datos mediante el evento OnUpdateData. Reset : este mtodo permite descargar los cambios y recuperar los datos del registro desde el Data Source.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Propiedades CanModify: propiedad de slo lectura que indica si el control puede modificar el valor del campo. Si el campo o dataset es de slo lectura, entonces, modified devuelve falso. Control: esta propiedad devuelve una referencia al control data. Cuando el control crea el objeto data link, debe asignar a esta propiedad el valor self. Editing : propiedad de slo lectura que devuelve true si el data source est en estado de edicin. Field : esta propiedad de slo lectura devuelve una referencia al campo de la base de datos o nil si el data link no est enlazado a un campo. FieldName: esta propiedad almacena el nombre del campo de la base de datos enlazado. Eventos OnActiveChange: como su propio nombre indica este evento es activado cua ndo la propiedad active del data source cambia. OnDataChange : este evento es llamado cada que el registro cambia. En este evento el programador debe recuperar los nuevos valores del campo desde el data source. OnEditingChange : este evento es activado cuando el data source se pone en estado de edicin, as como cuando deja de estarlo. OnUpdateData: este evento es llamado cuando el data source necesita actualizar los datos desde el control. En este evento debemos codificar los pasos necesarios para actualizar el registro de la base de datos. Slo se llamar a este evento si el campo ha sido modificado (el mtodo modified ha sido llamado). Obviamente, si estamos construyendo un componente de slo lectura no necesitaremos codificar nada en este mtodo. Una vez conocido el arsenal que tenemos a nuestra disposicin, es hora de utilizarlo: es la guerra! ;-)
Tenemos el cdigo fuente del componente original: perfecto, este es el caso ms sencillo. Basta con crear una propiedad ReadOnly y, en el momento de ir actualizar el contenido de la base de datos, comprobar previamente dicha propiedad. Partimos de un componente del que no tenemos el cdigo fuente: esto ya es ms complicado. Dependiendo de cmo se haya escrito el componente original (existencia de mtodos protected, etc), nuestra tarea puede ir de lo medianamente sencillo a lo totalmente imposible. :- (
Nuestro caso, aunque no lo parezca, es el segundo. Es cierto que con Delphi se entrega el cdigo fuente de los componentes, pero esto no quiere decir que lo podamos distribuir libremente. Se puede distribuir libremente el cdigo fuente de los componentes que nosotros desarrollemos, pero no del componente antecesor del nuestro si este forma parte de los controles estandar de Delphi. As que vamos a suponer que no disponemos del cdigo fuente del componente TTrackBar, lo cul har la tarea an ms interesante. Antes de proseguir un pequeo inciso: ojo si partimos de un componente que ya tenga la propiedad ReadOnly. Esto no indica nada. De hecho, tendramos que asegurarnos de sincronizar la propiedad ReadOnly del control con el estado real del data source. Un ejemplo de este caso es el control TEdit, que ya tiene la propiedad ReadOnly, la cual no tiene nada que ver con el estado del data source asociado; de hecho, el componente TEdit no est asociado a ninguna base de datos. Una vez hecha est aclaracin, veamos en nuestro caso cmo podemos implementar la propiedad ReadOnly en nuestro componente. La declaracin es sencilla:
TTrackBar = class(TrackBar) private FReadOnly : boolean; ... published property ReadOnly : boolean read FReadOnly write FReadOnly default False; ... end;
Ahora bien, un control en estado de slo lectura, no acepta las pulsaciones de teclas ni los clics del ratn. Cmo conseguir este efecto en nuestro componente? Tenemos dos posibilidades:
Reimplementar (override) los mtodos de mensajes KeyDown, MouseDown, etc. del componente ancestor. En cada uno de estos mtodos deberamos comprobar si el componente est en estado de slo lectura. En caso afirmativo, obviamos la pulsacin, mientras que en caso negativo, llamamos al mtodo heredado (con inherited MouseDown por ejemplo).
Esta es una aproximacin que suele funcionar en la mayora de los casos, pero que en nuestro caso concreto no lo hace. El problema radica en que, aunque el valor de la propiedad ReadOnly sea True, el componente sigue admitiendo las entradas procedentes del teclado y el ratn. Esto es debido a que el cdigo fuente del componente TTrackBar,
Manual de Creacin de Componentes en Delphi MBS para el LTIASI tal y cmo se puede comprobar en el cdigo fuente de la VCL, se lmita bsicamente a encapsular un control estndar de Windows 95. Por ello, el control ya ha procesado la tecla pulsada antes de ejecutar el cdigo introducido en el evento KeyDown, por lo que no le afecta nada de lo codificado en l. De modo que deshechemos en este caso concreto esta posibilidad, pero teniendo en cuenta que si estuvieramos descendiendo de un ancestor propio, este sera el procedimiento adecuado a seguir.
La segunda posibilidad consiste en implementar (override) el procedimiento de mensajes del control por medio del del mtodo WndProc.
La explicacin detallada de los procedimientos de mensajes de windows queda fuera del alcance de este captulo, ya que forma parte del propio ncleo de Windows, de modo que me limitare a exponer el funcionamiento bsico del mismo. Explicaciones detalladas pueden encontrarse en la ayuda en lnea de Delphi y en la ayuda del API de Windows. En cualquier caso, si quereis que dediquemos una unidad del curso a este tema, no teneis ms que escribirme. La explicacin del mtodo WndProc, cmo acabo de mencionar, de forma un tanto simplista, es la siguiente: Toda ventana en Windows tiene mecanismos estndar para gestionar los mensajes denominados manejadores de mensajes. En Delphi, la VCL define un sistema de distribucin de mensajes que traduce en llamadas a mtodos todos los mensajes Windows dirigidos a un objeto en particular. El diagrama siguiente muestra el sistema de distribucin de mensajes:
Cuando queramos procesar de una forma particular un mensaje en concreto, bastar con con redefinir el mtodo manejador del mensaje utilizando la directiva message (veremos un ejemplo de esto un poco ms adelante). Pero si esto no nos basta, y lo que queremos es que el mensaje no se llegue a distribuir a su manejador, debemos capturar el mensaje en el mtodo WndProc, ya que este mtodo filtra los mensajes antes de pasarlos a Dispatch, que a su vez los pasa finalmente al manejador de cada mensaje particular. Esto es lo que realizamos en nuestro componente:
TDBTrackBar = class(TTrackBar) ... protected procedure WndProc(var Message: TMessage); override; ... implementation ... procedure TDBTrackBar.WndProc(var Message: TMessage); begin if not (csDesigning in ComponentState) then begin {Si el control est en read only o no est en estado de edicin, obviamos las pulsaciones de teclas y los mensajes del ratn}
Primeramente comprobamos si estamos en tiempo de diseo. Si la respuesta es afirmativa, nos limitamos a llamar al procedimiento WndProc heredado. Esta llamada es fundamental, ya que si no la hacemos el componente nunca recibira los mensajes que le enva el sistema operativo y el resultado, por lo general, ser un cuelgue del programa o incluso del ordenador. Si estamos en tiempo de ejecucin, debemos capturar los mensajes de teclado y ratn y evitar que pasen al componente siempre que el componente est en estado de slo lectura. Para ello comprobamos el rango del mensaje pasado: si este es uno de los que queremos eliminar simplemente salimos del procedimiento sin llamar a WndProc. En caso contrario, somos buenos y dejamos que el resto de mensajes lleguen al componente :-) Con esto ya tenemos solucionado el tema de la propiedad ReadOnly. Aunque lo hemos solucionado con poco cdigo, no os engaeis: el tema de la captura de mensajes es uno de los temas ms complejos (y potentes) que hemos visto a lo largo del curso. Os recomiendo que antes de empezar a utilizar capturas de este tipo os leais muy detenidamente toda la ayuda disponible en Delphi y en el API sobre este tema. Os evitareis muchos disgustos ;-)
Implementando el DataLink
Ha llegado el momento de aadir a nuestro componente el enlace con la base de datos. Para ello tenemos que construir un objeto TDataLink, concretamente un TFieldDataLink, para conectar el componente a la base de datos. Veamos la declaracin completa de nuestro componente:
TDBTrackBar = class(TTrackBar) private FReadOnly : boolean; FDataLink : TFieldDataLink; function GetDataField : string; procedure SetDataField(const Value : string); function GetDataSource : TDataSource; procedure SetDataSource(Value : TDataSource); procedure UpdateData(Sender: TObject); procedure DataChange(Sender : TObject); procedure EditingChange(Sender : TObject); protected procedure CMExit(var Message: TWMNoParams); message CM_EXIT; procedure CNHScroll(var Message: TWMHScroll); message CN_HSCROLL; procedure CNVScroll(var Message: TWMVScroll); message CN_VSCROLL;
Primero declaramos el campo FDataLink de tipo TFieldDataLink. Ahora debemos crearlo en el constructor de nuestro componente y enganchar los eventos del mismo que nos interesan:
constructor TDBTrackBar.Create(AOwner : TComponent); begin inherited Create(AOwner); ControlStyle:=ControlStyle - [csReplicatable]; // El control no se permite en un DBCtrlGrid FReadOnly:=False; FDataLink:=TFieldDataLink.Create; FDataLink.OnDataChange:=DataChange; FDataLink.OnUpdateData:=UpdateData; // FDataLink.OnEditingChange:=EditingChange; Descomentar esta lnea para que el control tenga AutoEdit FDataLink.Control:=Self; end;
Una vez creado el DataLink, enganchamos a los eventos OnDataChange y OnUpdateData nuestros manejadores para los mismos. Adems, asignamos el valor de la propiedad Control del DataLink una referencia a nuestro componente. Por ltimo, dos cosas: primero: nuestro componente no tiene capacidades de replicacin por lo que quitamos dicho flag de la propiedad ControlStyle (de este modo no se podr utilizar el control en un TDBCtrlGrid). Segundo: Hay controles de base de datos que tiene capacidad de AutoEdicin (p.e. el control TEdit). En principio no queremos que nuestro componente tenga esta caractersca, pero si os interesa probarlo, basta con que descomenteis la lnea que asigna el evento OnEd itingChange. Adems de construir el DataLink, al final hay que destruirlo, o sea que:
destructor TDBTrackBar.Destroy; begin FDataLink.Free; FDataLink:=nil; inherited Destroy; end;
Una vez creado, el usuario querr poderlo utilizar ;-) de modo que vamos a proporcionarle las propiedades que todo control enlazado a datos tiene: DataSource y DataField. Comencemos con la propiedad DataSource:
El mtodo get de la propiedad no tiene ningn misterio: se limita a devolver el correspondiente DataSource de nuestro objeto DataLink. El mtodo set si es algo ms interesante. Cuando se asigna un nuevo valor a la propiedad, comprobamos que dic ho valor sea distinto de nil y, en caso afirmativo, llamamos al mtodo FreeNotification del nuevo data source asignado. Venga, una pausa para que consulteis en la ayuda en lnea que hace este mtodo. Espero... Os ha quedado claro? qu no demasiado? Pues para eso estoy yo, para intentar hacerlo un poco mejor que la ayuda en lnea. Supongamos que no aadimos la lnea Value.FreeNotification(self) e imaginemos que ya hemos terminado el componente y un usuario va a utilizarlo. Para ello, se crea en un mdulo de datos su tabla paradox (objeto TTable) y un TDataSource. Luego, en un form de su aplicacin coloca nuestro TDBTrackBar y asigna a la propiedad DataSource el Data Source que tiene en el mdulo de datos. Lo ms normal de mundo no?. Ahora resulta que el us uario cambia de opinin, va al mdulo de datos y borra el TDataSource. En este momento, nuestro componente tiene en la propiedad data source una referencia invlida (el objeto del mdulo de datos ha sido destruido). Esto provocar un error severo cuando seleccionemos nuestro componente y puede provocar el cuelgue del propio Delphi. Chungo verdad? Pues todo esto se evita aadiendo la famosa lnea. De este modo le estamos diciendo al objeto DataSource del mdulo de datos: notifcame cuando te vas a destruir para que yo limpie la referencia que apunta a t. Y esta limpieza se realiza cuando recibimos la notificacin:
procedure TDBTrackBar.Notification(AComponent : TComponent; Operation : Toperation); begin inherited Notification(AComponent,Operation); if (Operation=opRemove) AND (FDataLink<>nil) AND (AComponent=DataSource) then DataSource:=nil; end;
Primero comprobamos que haya un campo asignado en la propiedad Field del DataLink y, en caso negativo, se devuelve un valor arbitrario como posicin del TrackBar (en este caso se devuelve 0). En caso afirmativo, basta con leer el valor del campo de la base de datos mediante FDataLink.Field.AsInteger y asignarlo a la propiedad position del TrackBar. As de simple. Pero conviene tener en cuenta dos aspectos: 1. Por conveniencia, leemos el valor del campo como entero, ya que es lo que nos interesa a nuestros propsitos, ya que nuestro componente se enlazar a campos numricos de la base de datos. Si el usuario intenta enlazarlo con un campo no numrico, obtendr la excepcin 'xxx is not a valid numeric value', cosa perfectamente normal. 2. Una vez ledo el valor del campo, hay que actualizar el componente para que refleje el nuevo valor. Esta tarea variar mucho en funcin del componente , pero bsicamente consistir en asignar el valor del campo a una determinada propiedad y llamar al mtodo Paint para que se muestre el nuevo valor. En nuestro caso, basta con asignar el valor del
Manual de Creacin de Componentes en Delphi MBS para el LTIASI campo a la propiedad position del TrackBar, ya que dicha propiedad heredada se encargar de repintar el componente. Una vez que hemos aprendido a asignar el nuevo valor del campo al componente, nos queda aprender cmo hacer el paso contrario, es decir, cuando el usuario modifica el valor de la propiedad position del trackBar (mediante el teclado, el ratn, o por cdigo), dicho valor debe actualizarse en el campo de la base de datos. En la siguiente seccin veremos cmo hacerlo.
El mecanismo de implementar nuestro propio manejador de mensajes es simple: en la seccin de interface definimos los mtodos que deben responder a los mensajes junto con la directiva message seguida del nombre del mensaje a capturar. En la seccin de implementacin, nos limitamos a informar al DataLink de que se han producido cambios en el valor del campo (aunque donde realmente se han producido es en el propio componente y el llamar a Modified es una "declaracin de intenciones") y que el objeto DataLink debe tenerlo en cuenta cuando vaya a actualizar el registro de la base de datos.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Con esto, el objeto DataLink sabe que se han producido cambios, por lo que cuando sea necesario actualizar el registro (por una llamada a Post, movimiento del puntero del data set, etc), se activar el evento OnUpdateData. Slo tenemos que codificar en respuesta a este evento (recordemos que ya lo enganchamos al mtodo UpdateData del constructor), las instrucciones necesarias para actualizar el valor del campo de la base de datos, lo cul lo conseguimos asignando el nuevo valor a la propiedad DataLink.Field:
procedure TDBTrackBar.UpdateData(Sender: TObject); begin FDataLink.Field.AsInteger := Position; end;
Con esto ya casi est. Nos queda un ltimo paso. Una vez que el usuario del componente hace click con el ratn para modificar el valor de la propiedad position, informamos al DataLink de que el campo va a cambiar llamando al mtodo Modified. De este modo el objeto DataLink activar el evento OnUpdateData para asignar el nuevo valor al campo, y este evento se disparar cuando sea requerido por el data source. Pero tambien debemos disparar este evento cuando nuestro componente pierda el foco: as nos aseguramos de que el campo se actualizar correctamente. Para realizar esta tarea debemos reimplementar el mensaje CMExit, que se genera cuando un componente pierde el foco:
procedure CMExit(var Message: TWMNoParams); message CM_EXIT; ... procedure TDBTrackBar.CMExit(var Message: TWMNoParams); begin try FDataLink.UpdateRecord; { Actualizamos el data link} except SetFocus; { Si falla la actualizacin, mantenemos el foco en el control} raise; { y relanzamos la excepcin} end; inherited; end;
En este mtodo nos limitamos a llamar al mtodo UpdateRecord para forzar la actualizacin del campo de la base de datos. Si se produce una excepcin en este proceso (tipicamente por asignar un valor no vlido al campo, por clave duplicada, etc). mantenemos el foco en el control y relanzamos la excepcin para que la vea el usuario. Un detalle a destacar: el mtodo UpdateRecord no lo hemos visto al estudiar los mtodos del objeto TFieldDataLink ya que est definido en TDataLink, el antecesor de TFieldDataLink. Su cometido es obvio: forzar la llamada a OnUpdateData para que se asigne un nue vo valor al campo de la base de datos (recordar que previamente se ha debido tambin llamar al mtodo Modified, ya que en caso contrario el evento OnUpdateData no se disparara). Con esto hemos terminado nuestro componente. Tan slo nos queda registrarlo y disfrutar de l.
Manual de Creacin de Componentes en Delphi MBS para el LTIASI Por ltimo estoy recibiendo diversas sugerencias sobre temas para prximas unidades, por lo cul me gustara que me enviaseis ideas a mi direccin de correo electrnico. Entre los temas que me han sugerido hasta el momento destacan:
Ayuda en lnea para los componentes Desarrollo de controles ActiveX Componentes con conexin a mltiples campos de la base de datos