Qué son componentes? La librería visual de componentes (VCL).
Los componentes son las piedra angular de la programación en Delphi. Aunque la mayoría de los componentes representan partes visibles de la interfaz de usuario, existen también componentes no visuales, como por el ejemplo los objetos Timer y Database.
Un componente, en su definición más simple no es más que un objeto descendiente del tipo TComponent. Todos los componentes descienden en su forma más primitiva de TComponent, ya que TComponent proporciona las características básicas que todo componente debe tener: capacidad de ser mostrado en la paleta de componentes así como de operar a nivel de diseño de formulario.
Los componentes hacen fácil la programación en Delphi. En vez de tener que operar a nivel de unidades, el usuario de un componente simplemente tiene que pinchar en él y situarlo en la posición deseada de su form. Eso es todo: Delphi se encarga de lo demás.
Todos los componentes forman parte de la jerarquía de objetos denominada Visual Component Library (VCL). Cuando se crea un nuevo componente, este se deriva a partir de un componente existente (bien sea TComponent o algún otro más especializado) y se añade a la VCL.
Control de
acceso a un componente. Declaraciones privadas, protegidas,
públicas y
publicadas.
Object Pascal dispone de cuatro niveles de control de acceso para los campos, propiedades y métodos de un componente. Este control de acceso permite especificar al programador de componentes que parte de código puede acceder a que partes del objeto. De este modo se define el interface del componente. Es importante planear bien este interface, ya que así nuestros componentes serán facilmente programables y reutilizables.
A menos que se especifique lo contrario, los campos, propiedades y métodos que se añaden 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 también accesible (o inaccesible) en cualquier otra parte de la unidad.
A continuación se detallan los tipos de controles de acceso:
Privado:
ocultando los detalles de
implementación.
Declarando una parte de un componente (bien sea un campo, propiedad o método) privado (private) provoca que esa parte del objeto sea invisible al código externo a la unidad en la cuál se declara el objeto. Dentro de la unidad que contiene la declaración, el código puede acceder a esa parte del objeto como si fuera público.
La principal utilidad de las declaraciones privadas es que permiten ocultar los detalles de implementación del componente al usuario final del mismo, ya que estos no pueden acceder a la parte privada de un objeto. De este modo se puede cambiar la implementación interna del objeto sin afectar al código que haya escrito el usuario.
Protegido:
definiendo el interface del
programador.
Declarar una parte de un componente como protegido (protected) provoca, al igual que ocurría al declararlo privado, que el código externo a la unidad no pueda acceder a dicha parte (se hace oculta al código externo a la unidad). La diferencia principal entre declarar una parte de un objeto protegida o hacerla privada es que los descendientes del componente podrán hacer referencia a esa parte.
Este comportamiento es especialmente útil para la creación de componentes que vayan a descender de aquel que hemos creado.
Público:
definiendo el interface en tiempo de
ejecución.
Todo las partes de un objeto que declaremos públicas (public) podrán ser referenciadas por cualquier código ya sea interno o externo a la propia unidad. En este sentido, la parte pública identifica el interface en tiempo de ejecución de nuestro componente. Los métodos que el usuario del componente debe llamar deben ser declarados publicos, así como también las propiedades de sólo lectura, al ser sólo válidas en tiempo de ejecución.
Las propiedades declaradas públicas no aparecerán en el inspector de objetos.
Esta sección es tal vez la más importante a considerar al diseñar un componente. Cuando se diseñan componentes, se debe considerar cuidadosamente que métodos y propiedades deben ser públicas. Si el diseño es correcto, este permitirá retocar las estructuras de datos y métodos internos del componente sin tener que tocar el interface público, que seguirá siendo el mismo para el usuario del componente.
Publicado:
definiendo el interface en tiempo de
diseño.
Al declarar parte de un objeto publicado (published) provoca que la parte sea pública y además genera información en tiempo de ejecución para dicha parte.
Las propiedades declaradas publicadas aparecen en el inspector de objetos en tiempo de diseño. Y ya que sólo las partes publicadas aparecen en el inspector de objetos, estas partes definen el interface en tiempo de diseño de nuestro componente. En general sólo se deben declarar publicadas propiedades y no funciones o procedimientos (ya que lo único que logramos con ello es declararlas públicas).
Pasos
necesarios para crear un componente. El experto de
componentes.
A grandes rasgos, los pasos necesarios para crear un nuevo
componente son los siguientes:
Crear una unidad para el nuevo componente.
Derivar el nuevo componente a partir de otro existente, el cuál servirá de base para añadir las nuevas características deseadas.
Añadir las propiedades, eventos y métodos necesarios al nuevo componente.
Registrar el componente, incluyendo los bitmaps adicionales, ficheros de ayuda, etc.
Instalar el nuevo componente en la paleta de componentes.
De todos los pasos citados, hay uno que es especialmente importente: la elección del ascendiente a partir del cuál derivar el nuevo componente. Este paso es crucial, ya que una buena elección del ascendiente puede hacernos la tarea de añadir nuevas propiedades y métodos realmente fácil, mientras que una mala elección puede hacernos imposible llegar al objetivo propuesto.
Como base para la elección del ascendiente, conviene hacer notar las siguientes normas:
· 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 método Paint y de Canvas.
· TCustomControl - El punto de partida más común. Esta clase dispone de window handle, eventos y propiedades comúnes y, principalmente, canvas con el método 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 solución, 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 más recomendable la segunda opción: utilizar el experto de componentes.
Para abrir el experto de componentes basta con elegir File|New Component.
Nos aparecerá un cuadro de diálogo 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 cuál derivaremos nuestro componente.
· Palette Page: Indicaremos aquí la página de la paleta en la cuál queremos que aparezca el nuevo componente.
Una vez introducidos estos campos, al pulsar sobre OK se nos desplegará el código 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 generaría la siguiente unidad, lista para introducir las propiedades y métodos 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.
A partir de aquí todo consiste en introducir las propiedades y métodos necesarios para el funcionamiento de nuestro componente. Pero antes de nada conviene hacer notar algunos aspectos:
En la cláusula uses, Delphi añade por defecto las unidades standard. Si nuestro componente no usa alguna de ellas podemos eliminarla de dicha cláusula. Del mismo modo, si utilizamos algún procedimiento o función situado en otra unidad, debemos añadir dicha unidad a la cláusula uses.
Las declaraciones de las propiedades, campos y métodos que vayamos definiendo, las situaremos en la sección apropiada de interfaz según corresponda, es decir las declararemos privadas, protegidas, pública o publicadas.
Delphi declara y define automáticamente el procedimiento register para registrar el componente en la paleta.
Objetivo del componente
El propósito del componente es muy simple: a partir de un dato de entrada, el número de DNI, nuestro componente debe calcular la letra del NIF correspondiente. El método de calcular esta letra es mediante una operación matemática muy sencilla. Esta operación es: DNI - ((DNI div 23) * 23). El resultado de esta operación será un número comprendido entre 1 y 23. Mediante una tabla asignaremos una letra determinada a cada número, letra que corresponde al NIF pedido. Los detalles completos de este calculo y asignación de letra se encuentran en el código fuente.
Diseño del
componente
En base a nuestro objetivo, queda claro que nuestro componente será del tipo no visual y, por lo tanto, lo derivaremos a partir de TComponent, que como ya se ha visto; (unidad 2), es la base para la creación de componentes no visuales.
La forma de
introducir el número de DNI será mediante una propiedad
(propiedad DNI) de lectura y escritura. El valor de dicha
propiedad lo almacenaremos en un campo (FDNI) de tipo longInt.
Esta propiedad será publicada (published) para que así
aparezca en el inspector de objetos. La lectura y escritura de
valores en esta propiedad la haremos directamente sobre el
campo FDNI, es decir dejaremos que sea el propio inspector de
objetos el que se encargue de verificar que el valor
introducido corresponde al tipo longInt.
Tendremos otra propiedad (NIF) en la que se almacenará la
letra calculada (de tipo char). Pero esta propiedad será de
sólo lectura ya que el usuario del componente no debe poder
introducir la letra manualmente, ya que debe ser el propio
componente el que la calcule. Esta propiedad
debe de ser
pública (public) ya que es de sólo lectura. Este aspecto
conviene resaltarlo: las
propiedades de sólo lectura deben ir declaradas en la parte
pública. La función que se encarga de calcular la letra
del NIF la llamaremos GetNIF.
Los campos que almacenan el valor de propiedades siempre se
declararán 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 través 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 comenzarán 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 métodos de lectura y escritura de los valores de una propiedad se denominarán mediente el prefijo Get (para lectura) o Set (para escritura) seguidos del nombre de la propiedad. P.e. el método GetNIF.
Una vez acordado el método de diseño empleado es hora de comenzar a teclear.
Código fuente
del componente
unit Nif; { (c)1996 by Luis Roche }
interface
uses
Classes;
type
TNif = class(TComponent) {Nuestro propiedad deriva de TComponent}
private
FDNI : LongInt; {Almacenar?l n? de DNI}
function GetNIF : char; {Calcula la letra del NIF}
protected
public
property NIF: char read GetNIF; {Propiedad NIF: s?lectura}
published
property DNI: LongInt read FDNI write FDNI; {DNI: lectura y escritura}
end;
procedure Register; {Registra nuestro componente en la paleta}
implementation
function TNIF.GetNIF : char; {Calcula el NIF a partir del DNI}
Var aux1 : integer;
Const letras : string = 'TRWAGMYFPDXBNJZSQVHLCKE';
begin
aux1:=FDNI - ((FDNI div 23) * 23);
result:=letras[aux1+1];
end;
procedure register; {registro del componente}
begin
registercomponents('curso', [tnif]);
end;
end.
Comentarios
al código fuente
El código fuente se ha creado siguiendo los siguientes pasos:
· Utilizamos el experto de componentes para que nos generere la unidad en la que escribiremos nuestro componente. En el cuadro de diálogo 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 básico de nuestro componente, incluyendo el procedimiento Register.
· De todas las unidades que aparecen en la clausula uses dejamos sólo Classes, ya que no utilizamos ningún procedimiento de las demás.
·
Declaramos
el campo FDNI (longInt) en la sección privada. En este campo,
como ya se ha dicho, almacenaremos el DNI. En la sección
published escribimos la siguiente línea:
property DNI: LongInt
read FDNI write FDNI
De este modo declaramos la propiedad DNI y especificamos
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 sólo lectura en la parte public. Conviene recordar que las propiedades de sólo lectura deben ir declaradas en la parte pública y no en la publicada. Especificamos que para leer el valor de la propiedad utilizaremos la función GetNIF, la cuál declaramos en la sección privada.
· Escribimos la función que cálcula la letra del NIF en la parte de implementación 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)
Creando un
bitmap para el componente
Cada componente necesita un bitmap para representar al componente en la paleta de componentes. Si no se especifica uno, Delphi utilizará uno por defecto.
El bitmap no se incluye en el código fuente del componente, sino que debe incluirse en un archivo aparte con la extensión .DCR (dynamic component resource). Este fichero puede crearse con el propio editor de imagenes que incorpora Delphi.
El nombre del archivo .DCR debe coincidir con el nombre con que se ha salvado la unidad que contiene el componente. El nombre de la imagen bitmap (que debe estar en mayúsculas) 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 tamaño del bitmap debe ser de 24x24
pixels.
Como último detalle, si quieres utilizar este mismo bitmap, puedes utilizar un programa de tratamiento de imágenes para cortarlo y pegarlo en el editor de imágenes de Delphi.
Instalando
TNif en la paleta de componentes.
Instalar nuestro componente en la paleta de componentes es muy
sencillo. Basta con seguir los siguientes pasos:
· Elegir la opción Options|Install Components desde el menú de Delphi.
· Pulsar sobre el botón Add para seleccionar la unidad que contiene el código 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 compilación 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, caídas de tensión, etc.