Está en la página 1de 480

cm MenuChanged: Se envia despues de las operaciones de mezcla

de menus MDI o OLE.


Mensajes relacionados con teclas especiales:
cm-C h i l d Key: Se envia a1 control padre para controlar algunas te-
clas especiales (en Delphi, este mensaje solo lo controlan 10s compo-
nentes DBCtrlGrid).
cm D i a l o g c h a r : Se envia a un control que establezca si una tecla
de gntrada concreta es su caricter abreviado.
cm-D i a 1o g Ke y: Controlado por formularies modales y controles
que necesitan realizar acciones especiales.
cm-I s S h o r t C u t : No se usa actualmente (ya que la mayoria de codi-
go simplemente llama a Is S h o r t c u t ) , per0 esta pensado para ser
usado para identificar si un formulario da soporte a un acceso direct0 a
traves del evento OnS h o t c u t , un elemento del menu o una accion.
cm Want S p e c i a 1Key: Controlado por controles que interpretan
tecGs especiales de un mod0 poco usual (por ejemplo, usando la tecla
Tabulador para navegar como hacen componentes Grid).
Mensajes para componentes especificos:
cm-~ e t ~ a t a L i kn: Usado por controles DBCtrlGrid.
cm-TabFontChanged: Usado por 10s componentes TabbedNotebook.
cm B u t t o n P r e s s e d : Usado por SpeedButtons para notificar a otros
componentes SpeedButton parejos (para activar el comportarniento
boton de radio).
cm-De f e r L a y o u t : Usado por componentes DBGrid.
Mensajes de contenedor OLE: cm -D o c W i n d o w A c t i v a t e , cm-
I s T o o l C o n t r o l , cm - R e l e a s e , cm -U I A c t i v a t e y cm -
UIDeactivate.
Mensajes relacionados con el anclaje, como cm Doc k C l i e n t , cm-
D o c k N o t i f i c a t i o n , c m F l o a t y cm-~ n d o ~ k ~ l i e n t .
Mensajes de implementation de metodos, como cm R e c r e a t eW nd, lla-
mado dentro del metodo R e c r e a t e W n d d e - ~ ~ o n t r o lcm : -
I n v a l i d a t e , llamado dentro de T C o n t r o l . I n v a 1 i d a t e ; cm-
C h a n g e d , llamado dentro de T C o n t r o l . C h a n g e d y cm
A l l C h i l d r e n F l i p p e d , llamado en 10s mktodos ~ o ~ l i ~ ~ h i l d r
de T w i n c o n t r o l y T S c r o l l i n g W i n C o n t r o l . En el grupo similar
estan dos mensajes relacionados con listas de acciones cm-A c t i o n -
U p d a t e y cm-A c t i o n E x e c u t e .
Notificaciones a componentes
Los mensajes de notificacion a componentes son 10s que envia un formulario o
componente padre a sus hijos. Estas notificaciones corresponden a 10s mensajes
enviados por Windows a la ventana del control padre, per0 logicamente destina-
dos a1 control. Por ejemplo, la interaccion con controles como botones, cuadros
de edicion o cuadros de lista hace que Windows envie un mensaje wm Command
a1 padre del control. Cuando un programa de Delphi recibe estos mensajes, 10s
reenvia a1 mensaje del propio control, como una notificacion. El control de Delphi
puede controlar el mensaje y en ultimo termino producir un evento. Ocurren ope-
raciones similares para muchos otros mensajes. La conexion entre 10s mensajes
Windows y 10s de notificacion a componentes es tan estrecha que, por lo general,
reconoceremos el nombre de 10s mensajes Windows por el nombre del mensaje de
notificacion, sencillamente sustituyendo la cn inicial por wm.Estos son algunos
de 10s grupos distintivos de mensajes de notificacion a componentes:
Mensajes generales del teclado: cn-Char,cn-Ke yUp, cn-Ke yDown,
cn-SysChary cn-SysKeyDown.
Mensajes especiales del teclado utilizados solo por 10s cuadros de lista con
el estilo lbs-WantKeyboardInput: cn-CharToItem y cn-
VKe yToItem.
Mensajes relacionados con la tecnica de dibujo personalizado: cn-
C o m p a r e I t e m , c n - D e l e t e I t e m , c n- D r a w I t e m y c n -
MeasureItem.
Mensajes para desplazamiento, utilizados solo por controles de la barra de
desplazamiento y de la barra de seguimiento: c n - H S c r o 1 1 y
cn-VScroll.
Mensajes de notificacion generales, utilizados por la mayoria de 10s con-
troles: cn-Command, cn-Notify y cn-ParentNotify.
Mensajes de color de controles: cn CtlColorBtn,cn-CtlColorDlg,
cn-CtlColorEdit, cn-CtlColor~istbox, cn-CtlColor-
Msgbox, cn-CtlColorScrollbar y cn-CtlColorStatic.
Hay mas notificaciones de controles definidas para soporte de controles comu-
nes (en la unidad ComCtrls).

Un ejemplo de mensajes de componente


Como ejemplo del uso de algunos mensajes de componente, hemos creado el
programa CMNTest. Tiene un formulario con tres cuadros de edicion y etiquetas
asociadas. El primer mensaje que maneja, cm DialogKe y, permite tratar la
tecla Intro como si fuera la tecla Tab. El c6digi de este metodo busca el c6digo
de la tecla Intro y envia el mismo mensaje, per0 pasa el codigo de tecla v k -Tab.
Para evitar posteriores procesamientos de la tecla Intro, se asigna a1 resultado del
mensaje un 1:
p r o c e d u r e TForml.CMDialogKey(var Message: TCMDialogKey);
begin
if (Message.CharCode = VK-RETURN) t h e n
begin
Perform (CM-DialogKey, VK-TAB, 0 ) ;
Message .Result : = 1;
end
else
inherited;
end;

El segundo mensaje, cm DialogChar,monitoriza teclas abreviadas. Esta


tkcnica puede resultar util para crear accesos directos personalizados sin definir
un menu extra para ellos. Aunque este codigo es correct0 para un componente, en
una aplicacion normal esto puede lograrse mas facilmente controlando el evento
OnShortCut del formulario. En este caso. trazamos las teclas especiales en
una etiqueta:
p r o c e d u r e TForml.CMDialogChar(var Msg: TCMDialogChar);
begin
Labell.Caption : = Labell.Caption + Char (Msg.CharCode);
inherited;
end;

Finalmente, el formulario maneja el mensaje cmd F o cuschanged,para


responder a cambios de foco sin necesidad de controlarel evento OnEnter de
cada uno de estos componentes. Una vez mas, la accion muestra una descripcion
del componente que recibe el foco.
p r o c e d u r e TForml.CmFocusChanged(var Msg: TCmFocusChanged);
begin
Label5.Caption : = 'El foco e s t d en ' + Msg.Sender.Name;
end ;

La ventaja de esta tecnica esta en que funciona independientemente del tip0 y


numero de componentes que aiiadamos a1 formulario, y hace esto sin necesidad de
ninguna accion por nuestra parte. Es un ejemplo trivial para un concept0 tan
avanzado, per0 si le aiiadimos el codigo del componente ActiveButton, tendremos
a1 menos unas pocas razones para observar estos mensajes especiales no docu-
mentados. A veces, escribir el mismo codigo sin su soporte, puede volverse extre-
madamente complejo.

Un cuadro de dialog0 en un componente


A continuacion, examinaremos un componente totalmente diferente de 10s que
hemos visto hasta ahora. Despues de crear controles basados en ventanas y com-
ponentes graficos sencillos, vamos a crear ahora un componente no visual. La
idea basica es que 10s formularios son componentes. Cuando hemos creado un
formulario que podria resultar especialmente util cn varios proyectos, podemos
aiiadirlo a1 Object Repository o hacer de el un componente. La segunda tecnica es
mas compleja que la primera, pero hace que el uso del nuevo formulario resulte
mas sencillo y permite distribuir el formulario sin su codigo fuente. Como ejem-
plo, crearemos un componente basado en un cuadro de dialogo personalizado.
intentando imitar a1 masimo posible el comportamiento dc 10s componentes cua-
dro de dialogo estandar de Delphi.
El primer paso para construir un cuadro de dialogo en un componentc consiste
en escribir el codigo para el propio cuadro de dialogo, usando la tdcnica estandar
de Delphi. Definimos un nuevo formulario y trabajamos como siempre sobre el.
Cuando un componente se basa en un formulario, podemos diseiiar casi visualmente
el componente. Por supuesto, cuando se ha creado el cuadro de dialogo, tenemos
que definir un componente sobre el de un mod0 no visual.
El cuadro de dialogo estandar que vamos a construir se basa en un cuadro de
lista, porquc cs comun para dejar que un usuario escoja un valor de una lista de
cadenas. Hemos personalizado este comportamiento comun en un cuadro de dia-
logo y. despues. lo hemos utilizado para crear un componente. El formulario
simple ListBoxForm que hemos creado tiene un cuadro de lista y 10s tipicos
botones OK y Cancel; como se puede ver en su descripcion textual:
object M d L i s t B o x F o r r n : T M d L i s t B o x F o r r n
Borderstyle = bsDialog
C a p t i o n = 'ListBoxForm'
object L i s t B o x l : T L i s t B o x
OnDblClick = ListBoxlDblClick
end
object B i t B t n l : T B i t B t n
K i n d = bkOK
end
object B i t B t n 2 : T B i t B t n
Kind = bkCancel
end
end

El unico mdtodo de este formulario cuadro de dialogo se refiere a1 evento doble


clic del cuadro de lista, que cierra el cuadro de dialogo como si el usuario pulsase
el boton OK, configurando la propiedad ModalResult del formulario como
mr0 k . Cuando el formulario funciona, podemos comenzar a cambiar su codigo
fuente, aiiadiendo la definicion de un componente y eliminando la declaracion de
la variable global del formulario.

NOTA: Para componentes basados en un formulario, podemos usar dos


archivos de codigo fbente en Pascal: uno para el formulario y el otro para el
componente que lo encapsula. Tambien es posible colocar tanto el compo-
ejemplo. En teoria, seria mejor declarar la clase formulario en la parte de
implementacion de la unidad, oculthndola de 10s usuarios del componente.
Pero en la practica tsta no es una buena idea. Para manipular el formulario
visualmente en el Diseiiador de Formularios, la declaracion de la clase
formulario debe aparecer en la seccion de interfaz de la unidad. La logica
de este comportamiento de la IDE de Delphi esth, entre otras, en que mini-
miza la cantidad de codigo que el gestor de modulos debe cornprobar para
enwntrar la declaracion del formulario (una operacion que debe realizar a
menudo para mantener la sincronia del formulario visual con la definicion
de la clase).

Lo mas importante es la definicion del componente TMdLis t B o x D i a l o g .


Este componente se define como "no visual" porque su clase ascendente inmediata
es TComponent. El componente tiene una propiedad publica y estas tres propie-
dades publicadas:
Lines: Es un ob.jeto TS t r i n g s , a1 que se accede mediante dos metodos,
G e t L i n e s y S e t L i n e s . Este segundo metodo utiliza el procedimiento
A s s i g n para copiar 10s nuevos valores en el campo privado que corres-
ponde a esta propiedad. Este objeto interno se inicia en el constructor
C r e a t e y se destruye en el metodo D e s t r o y .
Selected: Es un entero que accede directamente a1 campo privado corres-
pondiente. Almacena el elemento seleccionado de la lista de cadenas.
Title: Es una cadena utilizada para cambiar el titulo del cuadro de dialogo.
La propiedad publica es sel Itern, una propiedad de solo lectura que recupe-
ra automaticamente el elemento seleccionado de la lista de cadenas. Fijese en que
esta propiedad no almacena ni tiene datos: sencillamente accede a otras propieda-
des, ofreciendo una representacion virtual de 10s datos:
type
TMdListBoxDialog = class (TComponent)
private
FLines: TStrings;
FSelected: Integer;
FTitle: string;
function GetSelItem: string;
procedure SetLines (Value: TStrings) ;
function GetLines: TStrings;
public
constructor Create(A0wner: TComponent); override;
destructor Destroy; override;
function Execute: Boolean;
property SelItem: string read GetSelItem;
published
property Lines: TStrings read GetLines write SetLines;
property Selected: Integer read FSelected write FSelected;
property Title: string read FTitle w r i t e FTitle;
end;

La mayoria del codigo de este ejemplo pertenece a1 metodo E x e c u t e , una


funcion que devuelve T r u e o Fa 1s e , dependiendo del resultado modal del cua-
dro de dialogo. Esto coincide con el metodo E x e c u t e de la mayoria de compo-
nentes de cuadro de dialogo estandar de Delphi. La funcion E x e c u t e crea el
formulario de forma dinamica, configura algunos de sus valores utilizando las
propiedades del componente, muestra el cuadro de dialogo y, si el resultado es
correcto, actualiza la selection actual:
f u n c t i o n TMdListBoxDialog.Execute: Boolean;
var
ListBoxForm: TListBoxForm;
begin
i f FLines .Count = 0 then
r a i s e EStringListError.Create ('No hay elementos en la
lista') ;
ListBoxForm : = TListBoxForm.Create ( S e l f ) ;
try
ListBoxForm.ListBoxl.Items : = FLines;
ListBoxForm.ListBoxl.ItemIndex : = FSelected;
ListBoxForm.Caption : = FTitle;
i f ListBpxForm.ShowModa1 = mrOk then
begin
Result : = True;
Selected : = ListBoxForm.ListBoxl.ItemIndex;
end
else
Result : = False;
finally
ListBoxForm. Free;
end ;
end;

El codigo esta dentro de un bloque t r y / f i n a l l y , de mod0 que si ocurre un


error de ejecucion a1 mostrar el cuadro de dialogo, el formulario sera destruido de
todas maneras. Hemos usado excepciones tambien para lanzar un error si la lista
esta vacia cuando un usuario lo ejecuta. Este es un error de diseiio, y usar una
excepcion es una buena tecnica para corregirlo. El resto de metodos del compo-
nente son sencillos. El constructor crea la lista de cadenas F L i n e s , que es borra-
da por el destructor; 10s metodos G e t L i n e s y S e t L i n e s operan sobre la
cadena como un todo, y la funcion G e t S e l I tern (mostrada a continuation)
devuelve el texto de un elemento dado:
f u n c t i o n TMdListBoxDialog.GetSel1tem: string;
begin
if (Selected >= 0 ) and (Selected < FLines.Count) then
Result : = FLines [Selected]
else
Result : = " ;
end ;

Por supuesto, dado que estamos escribiendo manualmente el codigo del com-
ponente y aiiadiendole codigo fuente a1 formulario original, tenemos que recordar
escribir el procedimiento R e g i s t e r .
Una vez escrito el procedimiento R e g i s t e r y preparado el componente, de-
bemos crear un mapa de bits. En 10s componentes no visuales, 10s mapas de bits
son fundamentales ya que no solo se usan en la Component Palette, tambien se
utilizan a1 colocar el componente en un formulario.

Uso del componente no visual


Tras preparar el mapa de bits e instalar el componente, hemos creado un pro-
yecto sencillo para probarlo. El formulario de este programa de prueba tiene un
boton, un cuadro de edicion y el componente MdL is t Dia 1o g . En el programa,
solo se han aiiadido unas pocas lineas de codigo, las correspondientes a1 evento
OnClic k del boton:
procedure TForml.ButtonlClick(Sender: TObject);
begin
/ / selecciona el texto del cuadro de edicion, s i corresponde
// a una de las cadenas
MdListDialogl.Se1ected : = MdListDialogl.Lines.Index0f
(Editl-Text) ;
// ejecuta el didlogo y obtiene el resultado
if MdListDialog1.Execute then
Editl.Text : = MdListDialogl.SelItem;
end ;

Eso es todo lo necesario para ejecutar el cuadro de dialogo que hemos coloca-
do en el componente, como se puede ver en la figura 9.10. Como hemos visto, esta
es una interesante tecnica para el desarrollo de algunos cuadros de dialogo comu-
nes.

Propiedades de coleccion
De vez en cuando, necesitamos propiedades que contengan una lista de valores
en lugar de uno solo. A veces, podemos usar una propiedad basada en
T S t r i n g L i s t , per0 solo es valido con informacion textual (aunque puede aso-
ciarse un objeto a cada string). Cuando necesitemos una propiedad que almacene
una matriz de objetos, la solucion mas adecuada a VCL es usar una coleccion. El
papel de las colecciones es, por diseiio, el de crear propiedades que contengan una
lista de valores. Son ejemplos de propiedades de coleccion en Delphi la propiedad
Colums del componente DBGrid y la propiedad Panels del componente
TStatusBar

one

three

Figura 9.10. El ejemplo ListDialDemo muestra el cuadro de dialogo encapsulado en


un componente ListDial.

Una coleccion es, basicamente, un contencdor de objetos de un tip0 concreto.


Por esta razon. para definir una coleccion, debemos heredar una nueva clase de
TCollection y otra de TCollectionItem.Esta segunda clase definc 10s
contenidos en la colcccion; la coleccion se crea pasandole la clasc de 10s objctos
que contendra.
La clase coleccion no solo manipula 10s elementos de la misma, sin0 que tam-
bien cs responsable de crear nuevos objctos cuando es llamado su metodo A d d .
No podcmos crear un objeto y afiadirlo despues a una coleccion existente. El
listado 9.3 muestra dos clases para 10s elementos y la coleccion, con su codigo
mas significativo:

Listado 9.3. Las clases para una coleccion y sus elementos.

type
TMdMyItem = class (TCollectionItem)
private
FCode: Integer;
FText: string;
procedure SetCode(const Value: Integer);
procedure SetText (const Value: string) ;
published
property Text: string read FText write SetText;
property Code: Integer read FCode write SetCode;
end;

TMdMyCollection = class (TCollection)


private
FComp : TComponent;
FCollString: string;
public
constructor Create (CollOwner: TCornponent) ;
function GetOwner: TPersistent; override;
procedure Update(1tem: TCollectionItem); override;
end;

constructor TMdMyCollection.Create (CollOwner: TComponent);


begin
inherited Create (TMdMyItem);
FComp : = CollOwner;
end:

function TMdMyCollection.Get0wner: TPersistent;


begin
Result : = FComp;
end;

procedure TMyCollection.Update(Item: TCollectionItem);


var
str: string;
i: Integer;
begin
inherited;
// a c t u a l i z a r t o d o en c u a l q u i e r c a s o . . .
str : = ' 1 .
for i : = 0 to Count - I do
begin
str : = str + (Items [i] as TMyItem) .Text;
i f i < Count - 1 then
str : = str + I - ' ;

end;
FCollString : = str;
end;

La coleccion debe definir el metodo GetOwner para que el IDE de Delphi lo


muestre correctamente en el editor de propiedades de coleccion. Por esta razon,
necesita un enlace a1 componente que lo contiene, el propietario de la coleccion
(almacenado en el campo FComp en el codigo). Podemos ver la coleccion de este
componente de muestra en la figura 9.1 1.
Cada vez que cambia la informacion en un elemento de coleccion, su codigo
llama a1 metodo C h a n g e d (pasando T r u e o F a l s e para indicar si el cambio se
limita a1 elemento o se refiere a1 conjunto de elementos en la coleccion). Como
resultado de esta llamada, la clase T C o l l e c t i o n llama a1 metodo virtual
U p d a t e , que recibe como parametro el elemento unico que solicita la actualiza-
cion, o n i l si han cambiado todos 10s elementos (y cuando en el metodochanged
es llamado con T r u e como parametro). Podemos sobrescribir este metodo para
actualizar 10s valores de otros elementos de la coleccion, de la propia coleccion o
del componente destino.
Figura 9.11. El editor de colecciones, con la Object TreeView y el Object Inspector
para el elemento coleccion.

En este ejemplo actualizamos un string con un resumen de la informacion de la


coleccion, que hemos afiadido a esta y que el componente contenedor mostrara
como una propiedad. Usar una coleccion dentro de un componente es sencillo.
Declaramos una coleccionj la creamos en el constructor, la liberamos a1 final y la
mostramos a traves de una propiedad:
type
TCanTest = class (TComponent)
private
FColl: TMyCollection;
function GetCollString: string;
public
constructor Create (downer: TComponent); override;
destructor Destroy; override;
published
property MoreData: TMyCollection read FCollwrite SetMoreData;
property CollString: string read GetCollString;
end;

constructor TCanTest.Create(a0wner: TComponent);


begin
inherited;
FColl : = TMyCollection.Create (Self);
end;

destructor TCanTest.Destroy;
begin
FColl. Free;
inherited;
end:

procedure TCanTest.SetMoreData(const Value: TMyCollection);


begin
FColl .Assign (Value);
end;

function TCanTest.GetCollString: string;


begin
Result := FColl.FCollString;
end;

Los elementos de la coleccion se almacenan en ficheros DFM junto a1 compo-


nente que 10s contiene, usando las marcas especiales i tern y 10s simbolos mayor
y menor que, como en este ejemplo:
o b j e c t MdCollectionl: TMdCollection
MoreData = <
item
Text = 'uno'
Code = 1
end
item
Text = 'dos'
Code = 2
end
item
Text = 'tres'
Code = 3
end >
end

Definicion de acciones personalizadas


Ademas de definir componentes personalizados, podemos definir y registrar
acciones estandar nuevas, que estaran disponibles en el Action Editor del compo-
nente Action List. Crear nuevas acciones no es dificil. Tenemos que heredar de la
clase TAc t i o n y sobrescribir algunos de 10s metodos de la clase basica. Debe-
mos sobrescribir tres metodos:
La funcion H a n d l e s T a r g e t devuelve si el objeto accion quiere contro-
lar la operacion del destino actual, que es por defect0 el control con el
foco.
El procedimiento U p d a t e T a r g e t puede definir la interfaz de usuario de
10s controles conectados con la accion, desactivando finalmente la accion
si la operacion no esta disponible.
Podemos implementar el metodo Exe c u t e T a r g e t para determinar el
codigo actual que se va a ejecutar, de mod0 que el usuario pueda seleccio-
nar sencillamente la accion y no tenga que implementarla.
Para mostrar esta tecnica en la practica, hemos implementado las tres accio-
nes, cortar, copiar y pegar, en un cuadro de lista, de un mod0 similar a1 de la VCL
para un cuadro de edicion (a pesar de que hemos simplificado un poco el codigo).
Hemos escrito una clase basica, que hereda de la clase generica TList-
ControlAction de la nueva unidad ExtActns. Esta clase basica,
TMdCustomListAct ion,aiiade cierto codigo comun, compartido por todas
las acciones especificas y publica una serie de propiedades de accion. Las tres
clases derivadas tienen su propio codigo ExecuteTarget.Veamos las cuatro
clases:
type
TMdCustomListAction = class (TListControlAction)
protected
function TargetList (Target: TObject): TCustomListBox;
function Getcontrol (Target: TObject): TCustomListControl;
public
procedure UpdateTarget (Target: TObject) ; override;
published
property Caption;
property Enabled;
property Helpcontext;
property Hint;
property ImageIndex;
property Listcontrol;
property Shortcut;
property SecondaryShortCuts;
property Visible;
property OnHint;
end;

TMdListCutAction = class (TMdCustomListAction)


public
procedure ExecuteTarget (Target: TObj ect) ; override;
end;

TMdListCopyAction = class (TMdCustomListAction)


public
procedure ExecuteTarget (Target: TObject) ; override;
end;

TMdListPasteAction = class (TMdCustomListAction)


public
procedure UpdateTarget (Target: TObject); override;
procedure ExecuteTarget (Target: TObject); override;
end;

El metodo HandlesTarget,uno de 10s tres metodos clave de las acciones,


lo proporciona la clase TLi stControlAction,con el siguiente codigo:
function TListControlAction.HandlesTarget(Target: TObject) :
Boolean;
begin
- Result : = ( (ListControl <> nil) or
(ListControl = nil) and (Target is TCustomListControl))
and
TCustomListControl (Target).Focused;
end:

El metodo Upda teTa rget, en cambio, tiene dos implementaciones diferen-


tes. La predefinida la ofrece la clase basica y la usan las acciones copiar y cortar.
Estas acciones se activan solo si el cuadro de lista objetivo tiene a1 menos un
elemento y si hay un elemento seleccionado en ese momento.
El estado de la accion pegar depende, sin embargo, del estado del portapa-
peles :
procedure TMdCustomListAction.UpdateTarget (Target: TObject);
begin
Enabled : = (TargetList (Target). Items .Count > 0)
and (TargetList (Target). ItemIndex >= 0);
end:

function TMdCustomListAction.TargetList (Target: TObject) :


TCustomListBox;
begin
Result : = GetControl (Target) as TCustomListBox;
end;

function TMdCustomListAction.GetControl(Target: TObject) :


TCustomListControl;
begin
Result : = Target as TCustomListControl;
end;

procedure TMdListPasteAction.UpdateTarget (Target: TObject);


begin
Enabled : = C1ipboard.HasFormat (CF-TEXT);
end;

La funcion TargetList utiliza la funcion GetControl de la clase


TLi stCont ro lAct ion,que devuelve el cuadro de lista conectado a la accion
en tiempo de disefio o el control de destino, el control cuadro de lista con el foco
de entrada.
Por ultimo, 10s tres metodos ExecuteTarget realizan sencillamente las
acciones correspondientes en el cuadro de lista de destino:
procedure TMdListCopyAction.ExecuteTarget (Target: TObject);
begin
with TargetList (Target) do
Clipboard.AsText : = Items [ItemIndex];
end;

procedure TMdListCutAction.ExecuteTarget(Target: TObject);


begin
w i t h TargetList (Target) d o
begin
C l i p b o a r d - A s T e x t : = Items [ItemIndex];
Items .Delete (ItemIndex);
end ;
end ;

p r o c e d u r e TMdListPasteAction.ExecuteTarget(Target: TObject);
begin
.
(TargetList ( T a r g e t )) Items .Add (Clipboard.AsText) ;
end;

Una vez escrito este codigo en una unidad y afiadido a un paquete (el paquete
MdPack, en este caso), el paso final consiste en registrar las nuevas acciones
personalizadas en una categoria dada. Esta se indica como el primer parametro
del procedimiento RegisterActions,mientras que el segundo es la lista de
clases de acciones que se van a registrar:
procedure Register;
begin
RegisterActions ( 'List ',
[TMdListCutAction, TMdListCopyAction,
TMdListPasteAction] , nil) ;
end;

Para probar el uso de estas tres acciones personalizadas, hemos creado el


ejemplo ListTest (incluido con el codigo fuente de este capitulo). Este programa
tiene dos cuadros de lista junto a una barra de herramientas que contiene tres
botones conectados a las tres acciones y un cuadro de testo para introducir nue-
vos valores.
El programa permite cortar, copiar y pegar elementos de un cuadro de lista.
Podemos pensar que no es nada especial pero lo raro es que el programa no time
codigo.

ADVERTENCIA: Para establecer una imagen para una accion (y para


definir 10s valores por defect0 de las propiedades en general) debemos usar
el tercer parimetro del procedimiento Regis t e rAc t ions, que es un
modulo de datos que contiene la lista de imhgenes y una lista de acciones
con 10s valores predefinidos. Como tenemos que registrar las acciones an-
tes de poder establecer dicho modulo de datos, necesitaremos de un doble
registro a1 desarrollar estas acciones. Este tema es bastante complejo, poi
10 que no se tratara aqui, pero puede encontrarse una description detallada
- .. ,-a .. I . . . .-,.
HC t ions / z I I u .nrm en las secclones "Kegrsrenngxanaara Acnons..
. v . . . ,.
en h t t p : / / w w w . b l o n g . c o m / ~ o n f e r e n c e s / B o r ~ o n 2 O O 2 /

y "StandardActions And Data Modules". .


Escritura de editores de propiedades
Crear componentes es una forma muy eficaz de personalizar Delphi, ayudando
a 10s desarrolladores a construir aplicaciones mas rapidamente sin necesidad de
tener un profundo conocimiento de las tecnicas de bajo nivel. El entorno Delphi
esta, ademas, abierto a extensiones. Particularmente, podemos extender el Object
lnspector escribiendo editores de propiedades personalizados y el Form Designer
afiadiendo editores de componentes.
Junto con estas tecnicas, Delphi ofrece interfaces internas para 10s
desarrolladores de herramientas complementarias. Usar estas interfaces. conoci-
das como API OpenTools, requiere una avanzada comprension del mod0 en que
el entorno Delphi trabaja y un buen conocimiento de multiples tecnicas avanzadas
que no son tratadas en este libro. Para encontrar informacion tecnica y algunos
ejemplos de estas tecnicas, podemos acudir a1 Apendice A.

NOTA: La API OpenTools en Delphi ha cambiado considerablemente a


lo largo del tiempo. Por ejemplo, la unidad DsgnIntf de Delphi 5 se ha
dividido en las unidades D e s i g n I n t f , D e s i g n E d i t o r s y otras uni-
dades especificas. Borland tambitn ha introducido interfaces para definir
10s conjuntos de metodos de cada tip0 del editor. Sin embargo, - - la mayoria
de 10s ejemplos mas senci Ilos, como 10s mostrados en este libro, compilan
casi sin modificaciones. P ara mayor infonnacibn, se puede estudiar el am-
~ l i codieo
o fuente en el dilGbl.ullu\C-..-~JT--I~ A -; A.
\ o u U l b G \ 1u " I D n V I
~ - I - L : p-- n-1-L:
"6 Y G I p I . YGIpu,

b updateWpack2 Borland ha publicado por vez un archivo de ayuda


con la documentacibn de la API OpenTools.

Cada editor de propiedades debera heredar de la clase abstracta T P r o -


p e r t y E d i t o r , que se define en la unidad D e s i g n E d i t o r s y ofrece una
implementation estandar de la interfaz I P r o p e r t y . Delphi ya define algunos
editores de propiedades especificos para cadenas (la clase TSt r i n g p r o p e r t y),
enteros (la clase T I n t e g e r P r o p e r t y ) , caracteres (la clase TChar P r o p e r t y),
enumeraciones (la clase T E numPr o p e r t y), conjuntos (la clase T S e t -
P r o p e r t y ) , por lo que en realidad podemos heredar el editor de propiedades de
uno para el tipo de propiedad con el que estamos trabajando. En cualquier editor
de propiedades personalizado, tenemos que definir de nuevo la funcion
G e t A t t r i b u t e s , de mod0 que devuelva un conjunto de valores que indiquen
las capacidades del editor. Los atributos mas importantes son p a V a l u e L i s t y
p a D i a l o g . El atributo p a V a l u e L i s t indica que el Object Inspector mos-
trara un cuadro combinado con una lista de valores (clasificada tambien siempre
que este definido el atributo p a S o r t L i s t ) ofrecido por la sobrescritura del
metodo G e t V a l u e s . El estilo del atributo p a D i a l o g activa un boton de tres
puntos en el Object Inspector, que ejecuta el metodo E d i t del editor.
Un editor para las propiedades de sonido
El boton sonido que creamos antes tiene dos propiedades de sonido relaciona-
das: SoundUp y SoundDown. En realidad, estas eran cadenas, por lo que pudi-
mos mostrarlas en el Object Inspector usando un editor de propiedades
predefinido. A pesar de todo, pedir al usuario que escriba el nombre de un sonido
del sistema o un fichero esterno no es muy amigable y puede ser una fuente de
crrores.
Podriamos crear un editor generic0 para manejar nombres de archivos, pero
queremos escoger tambien el nombre de un sonido de sistema. (Como dijimos
antes, 10s sonidos de sistema son nombres predefinidos de sonidos conectados con
operaciones de usuario, asociadas con archivos de sonido reales en la pequeiia
aplicacion Sounds del Panel de Control de Windows.) El editor que hemos crea-
do para cadenas de sonido permite que un usuario escoja un valor de una lista
desplegable o muestre un cuadro de dialog0 del que cargar y probar un sonido (de
un archivo de sonido o un sonido de sistema). Por esta razon, el editor de propie-
dades ofrece 10s metodos Edit y GetValues:
W
l
'
e
TSoundProperty = class (TStringProperty)
pub1 i c
function GetAttributes: TPropertyAttributes; override;
procedure GetValues(Proc: TGetStrProc); override;
procedure Edit ; override;
end:

1 TRUCQ: La convencibn predefinida en Delphi @ Morni- a h tc l a k do I


I editor de propiedades cop un nombrg gue acabc con Proper& y. &s la$, :)

I editom de componentes con un nombre t$e akabe eon Edit* .I


La funcion Ge tAt t ributes combina paValueLis t (para la lista des-
plegable) y 10s atributos paDialog (para el cuadro de edicion personalizado) y
tambidn clasifica las listas y permite la selection de la propiedad de diversos
componentes:
function TSoundProperty-GetAttributes: TPropertyAttributes;
begin
// e d i t o r , l i s t a ordenada, s e l e c c i d n m u l t i p l e
Result : = [paDialog, paMultiSelect, pavaluelist, paSortList];
end;

El mCtodo GetValues llama sencillamente a1 procedimiento que recibe como


parametro varias veces, una vez para cada cadena que quiere aiiadir a la lista
desplegable (como se ve en la figura 9.12):
procedure TSoundProperty.GetValues(Proc!: TGetStrProc);
begin
/ / o f r e c e una lista d e s o n i d o s d e sistema
Proc ( 'Maximize ' ) ;
Proc ( 'Minimize ' ) ;
Proc ( ' M e n u C o m n d '
Proc ( 'MenuPopup ' ) ;
P r o c ( 'RestoreDown '
end;

Una tecnica mejor seria estraer estos valores del Registro de Windows, donde
todos estos nombres estan almacenados. El metodo Edit es sencillo: crea y muestra
un cuadro de dialogo. Podriamos haber mostrado el cuadro de dialogo Abrir
directamente, per0 hemos decidido afiadir un paso intermedio para permitir a1
usuario probar el sonido. Esto es parecido a lo que Delphi hace con las propieda-
des graficas: primer0 abrimos la vista previa y cargamos el fichero, solo despues
de confirmar que es correcto. El paso mas importante es cargar el fichero y pro-
barlo antes de aplicarlo a la propiedad. Este es el codigo del metodo Edit:
p r o c e d u r e TSoundProperty.Edit;
begin
S o u n d F o r m : = TSoundForm.Create ( A p p l i c a t i o n ) ;
t rY
SoundForm.ComboBoxl.Text : = GetValue;
/ / m o s t r a r e l c u a d r o d e didlogo
i f SoundForm.ShowModal = mrOK t h e n
SetValue (SoundForm.ComboBoxl .Text) ;
finally
SoundForm.Free;
end;
end;

1 IAN shown I
Figura 9.12. La lista de sonidos ofrece una pista al usuario, que tambien puede
escribir el valor de la propiedad o hacer doble clic para activar el editor (mostrado
despues, en la figura 9.13).

Los metodos GetValue y Setvalue son definidos por la clase basica, el


editor de propiedades de cadena. Leen y escriben el valor de la propiedad del
componente que estamos editando. Como alternativa, podemos acceder a1 compo-
nente que estamos editando usando el mdtodo Getcomponent (que requiere un
parametro indicando en cual de 10s componentes seleccionados estamos trabajan-
do, 0 indica el primer componente). ~ u a n d oaccedemos a1 componente directa-
mente, debemos tambien llamar a1 metodo Modified del objeto Designer
(una propiedad del editor de propiedades de la clase basica). No necesitamos esta
llamada a Modified en el ejemplo, porque el metodo Setvalue de la clase
base hace esto automaticamente por nosotros.
El metodo Edit anterior muestra un cuadro de dialogo (un formulario Delphi
estandar que se crea visualmente y que se aiiade a1 paquete que contiene 10s
componentes de tiempo de diseiio). El formulario es bastante simple; un cuadro
combinado muestra 10s valores devueltos por el metodo GetValues, y 10s cua-
tro botones nos permiten abrir un fichero, probar un sonido y cerrar el cuadro de
dialogo aceptando 10s valores o cancelandolos. Podemos ver un ejemplo del cua-
dro de dialogo en la figura 9.13. Proveer una lista desplegable de valores y un
cuadro de dialogo para editar una propiedad, hace que el Object Inspector
muestre solo el boton con flecha, que indica una lista desplegable, y omite el
boton eliptico, que indica que hay disponible un editor de cuadros de dialogo. En
este caso, como ocurre en el editor de propiedades por defect0 Color, el cuadro
de dialogo se obtiene haciendo doble clic sobre el valor actual o pulsando Con-
trol-Intro.

Sand Fae: I~enuCommand

d . . 1 IlmIx ~ d l
~p -- - - --

Figura 9.13. El forrnulario del editor de propiedades de sonido muestra una lista de
sonidos disponibles y nos perrnite cargar un fichero y escuchar el sonido
seleccionado.

Los dos primeros botones del formulario tienen un metodo asignado a su even-
to OnClick:
procedure TSoundForm.btnLoadClick(Sender: TObject);
begin
i f 0penDialogl.Execute then
ConboBoxl.Text := 0penDialogl.FileName;
end ;

procedure TSoundForm.btnPlayClick(Sender: TObject);


begin
PlaySound (PChar (CornboBoxl.Text), 0, snd-Async);
end ;

Es complicado determinar si un sonido esta debidamente definido y disponible


(podriamos comprobar el fichero, per0 10s sonidos del sistema crean algunos
problemas). La funcion Playsound devuelve un codigo de error si no encuentra
el sonido del sistema por defecto, que intenta reproducir cuando no encuentra el
sonido solicitado. Si el sonido solicitado no esta disponible, reproduce el sonido
del sistema por defecto y no devuelve el codigo de error. P l a y s o u n d busca
primero el sonido en el Registro y, si no lo encuentra ahi, comprueba si el fichero
de sonido especificado existe.

TRUCO: Si queremos amplid este ejemplo, pademos afiadir g r a f i c o ~a la


lista desplegable del Objed lirspector (si p$demos decidir q u t gr%fico
asociar a un sonido en particular).

Instalacion del editor de propiedades


Despues de escribir este codigo, podemos instalar el componente y su editor de
propiedades en Delphi. Para instalar el componente y su editor de propiedades en
Delphi, tenemos que aiiadir las siguientes sentencias al procedimiento R e g i s t e r
de la unidad:
procedure Register;
begin
RegisterPropertyEditor (TypeInfo(string), TMdSoundButton,
' S o u n d U p ' , TSoundProperty) ;
RegisterPropertyEditor (TypeInfo(string), TMdSoundButton,
' S o u n d D o w n f , TSoundProperty) ;
end;

Esta llamada registra el editor especificado en el ultimo parametro para usar


con propiedades del tip0 string (el primer parametro), per0 solo para un com-
ponente especifico y para una propiedad con un nombre especifico. Estos dos
ultimos valores se pueden omitir para ofrecer mas editores generales. Registrar
este editor permite al Object Inspector mostrar una lista de valores y el cuadro de
dialog0 al que llama el metodo E d i t .
Para instalar este componente, podemos aiiadir simplemente su codigo fuente
a un paquete nuevo o existente. En lugar de aiiadir esta unidad y las otras al
paquete MdPack, hemos creado un segundo paquete, que contiene todos 10s afia-
didos incorporados en el capitulo. Su nombre es MdDesPk. Lo nuevo de este
paquete es que hemos compilado utilizando la directiva de compilador
($DESIGNONLY). Esta directiva se usa para marcar paquetes que pueden
interactuar con el entorno Delphi, instalando componentes y editores, per0 que las
aplicaciones que hemos creado no necesitan en tiempo de ejecucion.
- - - -
NOTA: El codigo fuente de todas las lwmierrtcua adicionales esta en el
subdirectorio MdDesPk?junto cone1 &dig0 ddpaqueteutilizado para ins-
talarlas. No hay ejemplos que demuestren el modo & utilizacion de dichas
modo en que sebconapartur. .
I
La unidad del editor de propiedades usa la unidad SoundB, que define el
componente TMdSoundButton. Por esa razon, el nuevo paquete deberia refe-
rirse a1 paquete existente. Veamos el codigo inicial (aiiadiremos otras unidades
mas adelante en este capitulo):
package MdDes Pk;
( $ R *. RES]
($ALIGN O N ]
...
($DESCRIPTION 'Mastering Delphi DesignTime Package '1
( S D E S I G N O N LY ]

requires
vcl,
Mdpack ,
designide;

contains
PeSound i n ' PeSound. pas ',
PeFSound i n ' PePSound. pas ' (SoundPorm];

Creacion de un editor de componentes


Usar editores de propiedades permite a1 desarrollador hacer que el componente
sea mas comodo para el usuario. De hecho, el Object Inspector representa una
de las piezas clave de la interfaz de usuario del entorno Delphi y 10s desarrolladores
Delphi lo usan con mucha frecuencia. Sin embargo, existe una segunda tecnica
para personalizar el mod0 en que un componente interactua con Delphi: escribir
un editor de componentes personalizado.
A1 igual que 10s editores de propiedades amplian el Object Inspector, 10s
editores de componentes amplian el Form Designer. De hecho, cuando hacemos
clic con el boton derecho del raton sobre un formulario en tiempo de diseiio,
vemos algunos elementos de menu predefinidos, ademas de elementos aiiadidos
por el editor de componentes del componente seleccionado. Como ejemplos de
dichos elementos del menu estan 10s utilizados para activar el Menu Designer,
el Fields Editor, el Visual Query Builder y otros editores del entorno. A veces,
mostrar estos editores se convierte en la accion predefinida si se hace doble clic
sobre ellos.
Entre 10s usos comunes de 10s editores de componentes se encuentra aiiadir un
cuadro "Acerca de" con informacion sobre el desarrollador del componente, aiia-
dir el nombre del componente y ofrecer asistentes especificos para definir sus
propiedades. Particularmente, la intencion original era permitir a un asistente, o a
algun codigo directo, establecer multiples propiedades de golpe, en lugar de ha-
cerlo de uno en uno.

Subclasificacion de la clase TComponentEditor


Por lo general, un editor de componentes deberia heredar de la clase
TComponentEditor, que ofrece la implementation basica de la interfaz
IComponentEditor.Los metodos mas importantes de esta interfaz son:
GetVerbCount: Devuelve el numero de elementos del menu que hay que
aiiadir a1 menu local del Form Designer cuando se selecciona el compo-
nente.
GetVerb: Se llama una vez para cada uno de 10s nuevos elementos del
menu y deberia devolver el texto que ira en el menu local de cada uno.
Executeverb: Se llama cuando se selecciona uno de 10s nuevos elementos
de menu. El numero del elemento se pasa como el parametro del metodo.
Edit: Se llama cuando el usuario hace doble clic sobre el componente en el
Form Designer para activar la accion predefinida.
Cuando nos acostumbramos a la idea de que un "verbo" (verb) no es otra cosa
que un nuevo elemento del menu con una accion correspondiente que ha de ejecu-
tar, 10s nombres de 10s metodos de esta interfaz resultan bastante intuitivos. Esta
interfaz es mucho mas simple que las que hemos visto para 10s editores de propie-
dades.

Un editor de componentes para ListDialog


Despues de presentar las ideas clave sobre la escritura de editores de compo-
nentes, podemos ver un ejemplo, un editor para el componente ListDialog. En el
editor de componente que hemos creado, simplemente vamos a poder mostrar un
cuadro Acerca de, aiiadir el copyright a1 menu (un inapropiado per0 habitual uso
de 10s editores de componentes) y permitir que 10s usuarios realicen una accion
especial (obtener una vista previa del cuadro de dialogo conectado con el compo-
nente de dialogo. Tambien queremos cambiar la accion predefinida para mostrar
el cuadro Acerca de tras un sonido (que no es particularmente util per0 muestra
esta tecnica).
Para implementar este editor de propiedades, el programa habra de sobrescribir
10s cuatro metodos expuestos anteriormente:
uses
DesignIntf;
type
TMdListCompEditor = c l a s s (TComponentEditor)
f u n c t i o n GetVerbCount: Integer; override;
f u n c t i o n GetVerb (Index: Integer) : string; override;
p r o c e d u r e ExecuteVerb(1ndex: Integer); override;
p r o c e d u r e Edit; o v e r r i d e ;
end;

El primer metodo sencillamente devuelve el numero de elementos del menu que


afiadiremos a1 menu local, en este caso 3 . A este metodo se le llama so10 una vez,
antes de mostrar el menu. En cambio, a1 segundo metodo se le llama para cada
elemento del menu, por lo tanto, tres veces:
f u n c t i o n TMdListCompEditor.GetVerb (Index: Integer): string;
begin
c a s e Index o f
0: Result : = ' MdListDialog (Cantu)';
1: Result : = '&Acerca de es te componente. . . ';
.
2: Result : = '&Vista previa.. ';
end;
end;

El efecto de este codigo es aiiadir 10s elementos del menu a1 menu local del
formulario, como se ve en la figura 9.14. A1 seleccionar cualquiera de dichos
elementos de menu, simplemente se activa el metodo Executeverb del editor
de componentes:
p r o c e d u r e TMdListCompEditor.ExecuteVerb (Index: Integer);
begin
c a s e Index o f
0: ; // nada que hacer
1: MessageDlg ('Este es un sencillo editor de
componentes '#I3 + 'creado por Marco Cantu1#13 + 'para el libro
"La biblia del Delphi " ' , mtInf ormation, [mbOK] , 0) ;
2: w i t h Component a s TMdListDialog d o
Execute;
end;
end;

Hemos decidido manejar 10s dos primeros elementos en una unica rama de la
sentencia case, a pesar de que podiamos habernos saltado el mensaje de co-
pyright. El otro comando llama al metodo Execute del componente que estamos
editando, determinado usando la propiedad Component de la clase TCompo-
nent E d i t or. Conociendo el tipo del componente, podemos facilmente acceder
a sus metodos despues de una asignacion de tipos dinamica. El ultimo metodo se
refiere a la accion predefinida del componente y se activa haciendo doble clic
sobre el en el Form Designer:
procedure TMdListCompEditor.Edit;
begin
// p r o d u c e un s o n i d o y rnuestra e l c u a d r o A c e r c a d e
Beep;
Executeverb (0);
end:

Figura 9.14. Los elementos del menu personalizado atiadidos por el editor de
propiedades del componente ListDialog.

Registro del editor de componentes


Para que el editor estk disponible en el entorno Delphi, tenemos que registrar-
lo. De nuevo, aiiadimos a su unidad un procedimiento R e g i s t e r y llamamos a
un procedimiento de registro especifico para 10s editores de componentes:
procedure Register;
begin
RegistercomponentEditor (TMdListDialog, TMdListCompEditor);
end;

Hemos aiiadido esta unidad a1 paquete M d D e s P k, que incluye todas las exten-
siones en tiempo de diseiio del capitulo. Despues de instalarla y activar este pa-
quete se puede crear un nuevo proyecto, colocar una componente de lista con
solapa en el y experimentar.
paquetes

Los archivos ejecutables de Windows pueden tener dos formatos: programas


(EXE) y bibliotecas de enlace dinamico (DLL). Cuando escribimos una aplica-
cion Delphi, generalmente creamos un archivo de programa. Pero las aplicaciones
Delphi utilizan a menudo llamadas a funciones almacenadas en bibliotecas din&
micas. Cada llamada directa a una funcion de la API de Windows, accede real-
mente a una biblioteca dinamica. Es sencillo generar una DLL en el entorno
Delphi, aunque pueden surgir algunos problemas debido a la naturaleza de las
DLL. Escribir una biblioteca dinamica en Windows no es siempre tan simple
como parece, ya que la biblioteca dinamica y el programa que la llama, deben de
acordar las condiciones de la llamada, el tipo de parametros y otros detalles. Este
capitulo describe 10s fundamentos de la programacion de las DLL desde el punto
de vista de Delphi. La segunda parte del capitulo se centrara en un tipo especifico
de biblioteca de enlace dinamico: el paquete Delphi. Los paquetes Delphi ofrecen
una buena alternativa a las DLL simples, a pesar de que pocos programadores les
sacan partido si no es para el desarrollo de componentes. Aqui veremos algunos
trucos y tecnicas para utilizar paquetes para dividir en partes mas pequeiias una
aplicacion grande. Este capitulo comenta 10s siguientes temas:
Creacion y utilizacion de las DLL en Delphi.
Llamadas a funciones DLL en tiempo de ejecucion.
Compartir datos en las DLL.
Estructura de 10s paquetes Delphi.
Inclusion de formularios en paquetes.

La funcion de las DLL en Windows


Antes de comentar el desarrollo de las DLL en Delphi y otros lenguajes de
programacion, haremos un breve repaso tecnico sobre las DLL en Windows,
resaltando 10s elementos clave. Comenzaremos examinando el enlace dinamico, a
continuacion el mod0 en que Windows usa las DLL, exploraremos las diferencias
entre las DLL y 10s archivos ejecutables y, por ultimo, hablaremos de normas
generales sobre la escritura de DLL.

El enlace dinamico
En primer lugar, es necesario conocer la diferencia entre el enlace dinamico y
el enlace estatico a funciones o procedimientos. Cuando una subrutina no esta
disponible directamente en un archivo fuente, el compilador aiiade la subrutina a
una tabla de simbolos interna. El compilador, debe haber visto, por supuesto, la
declaracion de la subrutina y conocer sus parametros y tip0 o producira un error.
Tras la compilacion de una subrutina normal (estatica), el editor de enlaces
obtiene el codigo compilado de la subrutina desde una unidad compilada de Delphi
(o biblioteca estatica) y lo aiiade a1 codigo del programa. El archivo ejecutable
resultante incluye todo el codigo del programa y de las unidades relacionadas. El
editor de enlaces de Delphi es lo suficientemente inteligente como para incluir la
cantidad minima de codigo de las unidades utilizadas por el programa y enlazar
so10 las funciones y mktodos que en realidad se utilizan.

NOTA: Una exception que cabe destacar a esta norma es la inclusih de


m&odosvirtuales. El compilador no puede establecer con anticipacibn quC
metodos virtuales va a llamar el programa, por lo que tiene que incluirlos
todos. Por dicha razdn, 10s programas y bibliotecas con demasiadas fincio-
,
a
, ,;&.,I,, ,,,,A ,,-,I,&&, ..,,l.:..,, ,.:,-.&,Ll,, ,A, ,
*
,
a
, xrc:,,

.
mano 3 .
la flexittilidad obtenida rnediante las funciones virtuales y el reducido ta-
- - ejecurames
ae 10sarcmvos d .- .
que se - '-
L . X . -
conslgue I.
Ilmltanao -
el uso ae esas .r 3 1 .
funciones virtuales.

En el caso del enlace dinamico, que tiene lugar cuando nuestro codigo llama a
una funcion basada en una DLL, el editor de enlaces sencillamente utiliza la
informacion de la declaracion externa de la subrutina para instalar algunas tablas
en el archivo ejecutable. Cuando Windows carga el archivo ejecutable en memo-
ria, carga primer0 todas las DLL necesarias y, a continuacion, arranca el progra-
ma. Durante este proceso de carga, Windows rellena las tablas internas del
programa con las direcciones de las funciones de las DLL en memoria. Si por
alguna razon no se encuentra la DLL o una rutina referenciada no esta en una
DLL encontrada, el programa ni siquiera arranca.
Cada vez que el programa llama a una funcion externa, utiliza esta tabla inter-
na para reenviar la llamada a1 codigo de la DLL (que ahora esta situado en el
espacio de direcciones del programa). Fijese en que en esta estructura no hay dos
aplicaciones diferentes. La DLL se transforma en parte del programa en ejecucion
y se carga en el mismo espacio de direcciones. Todo el paso de parametros tiene
lugar en la pila de la aplicacion (porque la DLL no tiene una pila aparte) o en 10s
registros del procesador. Dado que una DLL se carga en cl espacio de direcciones
de la aplicacion, cualquier asignacion de memoria de la DLL o cualquier informa-
cion global que esta crea, reside cn el espacio de direcciones del proceso princi-
pal. Por ello, pueden pasarse informacion y punteros a memoria directamente
entre la DLL y el programa. Esto puede extenderse a1 paso de referencias a obje-
tos, lo que puede resultar problematico porque el ejecutable y la DLL pueden
tener una clase compilada diferente (para solucionar esto se pueden utilizar 10s
paquetes, como se vera posteriormente en este capitulo).
Existe otra tecnica de uso de las DLL aun mas dinamica que la que acabamos
dc mcncionar. Dc hccho, cn ticmpo dc cjccucion, podemos cargar una DLL en
memoria, buscar una funcion (siempre que sepamos su nombre) y llamar a la
funcion por su nombre. Esta tecnica requiere un codigo mas complejo y emplea
mas tiempo en localizar la funcion. Sin embargo, la ejecucion de la funcion posee
la misma velocidad que la llamada de una DLL cargada de forma implicita. Por el
contrario, no es necesario que la DLL este disponible a1 arrancar el programa.
Usaremos este enfoque mas adelante en el ejemplo DynaCall.

Uso de las DLL


Ahora que tenemos una idea general del mod0 en que funcionan las DLL,
podemos centrarnos en las razones para utilizarlas. La primera ventaja es que si
tenemos programas distintos que usan la misma DLL, esta se cargara en memoria
solo una vez y se ahorrara asi memoria de sistema. Las DLL se proyectan sobre el
cspacio de direcciones privado de cada proceso (cada aplicacion en funciona-
miento). per0 su codigo solo se carga en memoria una vez.
. . -- -- - = - --

NOTA: El sistema operativo intentara cargar la DLE en la misma direc-


cion de cada espacio de direcciones de la aplicacion (usando la direccion
bhica que se prefiera, especificada por la DLL). Si dicha direccion no e s d
cion, la imagen del codigo de la DLL de dicho proceso se tendra que volver
a colocar, una operacion costosa tanto en terminos de rendimiento como de
uso de memoria. La razon es que esa nueva asignacion se realiza en funci6n
de cada proceso y no de todo el sistema.

Otra interesante ventaja es que podemos ofrecer una version diferente de una
DLL. que sustituya a la actual. Si las subrutinas de la DLL tienen 10s mismos
parametros, podemos ejecutar el programa con la nueva version de la DLL sin
tener que volver a compilarlo. No importa en absoluto que la DLL tenga subrutinas
nucvas. Solo puede haber problemas si falta una rutina de la version antigua de la
DLL cn la nueva. Tambien puede haber dificultades si la nueva DLL no implementa
las funciones de una forma compatible con el funcionamiento de la antigua DLL.
Esta segunda ventaja es aplicable particularmente a aplicaciones complejas.
En caso de tener un programa muy grande que requiera actualizaciones y correc-
ciones frecuentes, dividirlo en multiples archivos ejecutables y bibliotecas dina-
micas nos permitira distribuir unicamente las partes modificadas en lugar de un
solo ejecutable de gran tamaiio. Hacer esto es especialmente importante con las
bibliotecas del sistema de Windows: generalmente, no es necesario recompilar
nuestro codigo si Microsoft publica una version actualizada de las bibliotecas del
sistema de Windows (en una nueva version del sistema operativo o en un paquete
de actualizacion).
Otra tecnica habitual es usar las bibliotecas dinamicas solo para almacenar
recursos. Podemos crear diferentes versiones de una DLL que contenga cadenas
de texto para diferentes idiomas y asi cambiar el idioma en tiempo de ejecucion, o
preparar una biblioteca de iconos e imagenes y utilizarlos en diferentes aplicacio-
nes. El desarrollo de versiones de un programa adaptadas a varios idiomas es muy
importante y Delphi incluye soporte para esto mediante su entorno integrado de
traduccion, el Integrated 7'ranslntion Envrronment (ITE).
Otra ventaja clave es que las DLL son independientes del lenguaje de progra-
macion. La mayoria de 10s entornos de programacion Windows, asi como la ma-
yoria de 10s lenguajes de macro de aplicaciones para usuarios finales, permiten a1
programador llamar a una funcion almacenada en una DLL. Esta flexibilidad se
aplica solo para el uso de funciones. Para compartir ob-jetos en una DLL entre
lengua-jes de programacion, deberiamos cambiar a la infraestructura COM o a la
arquitectura .NET.

Normas de creacion de DLL en Delphi


Esisten ciertas normas para 10s programadores de DLL en Delphi. Una fun-
cion o procedimiento DLL a1 que van a llamar 10s programas externos habra de
seguir las siguientes directrices:
Tendra que aparecer en la lista de la clausula exports de la DLL. Esto
hace que la rutina sea visible para 10s programas externos.
Las funciones exportadas deberian declararse tambien como stdcall,
para utilizar la tecnica de paso de parametros estandar de Win32, en lugar
de la tecnica de paso de parametros optimizada reg is ter (predefinida
en Delphi). La excepcion a esta norma es si queremos usar estas bibliote-
cas solo desde otras aplicaciones Delphi. Podemos usar otra tecnica de
llamada, suponiendo que el otro compilador la entienda (como cdecl,
que es la que utilizan por defect0 10s compiladores de C).
Los tipos de parametros de una DLL deberian ser tipos predefinidos de
Windows (sobre todo, tipos de datos compatibles con C), a1 menos si que-
remos ser capaces de usar la DLL en otros entornos de desarrollo. Aun hay
mas normas para la exportacion de cadenas de caracteres, como se vera en
el ejemplo FirstDLL.
Las DLL pueden utilizar datos globales que no compartiran las aplicacio-
nes que las llamen. Cada vez que una aplicacion carga una DLL, almacena
10s datos globales de la DLL en su propio espacio de direcciones, como
veremos en el ejemplo D11Mem.
Las bibliotecas Delphi deberian capturar todas las excepciones internas,
salvo que pretendamos usar la biblioteca solo desde otros programas Delphi.

Uso de las DLL existentes


Ya hemos utilizado bibliotecas DLL ya existentes en diversos ejemplos, a1
llamar a funciones de la API de Windows. Todas las funciones de la API se
declaran en la unidad de sistema Windows. Las funciones se declaran en la parte
de interfaz de la unidad, como vemos a continuacion:
function PlayMetaFile (DC: HDC; MF: HMETAFILE) : BOOL; stdcall;
function PaintRgn (DC: HDC; RGN: HRGN) : BOOL; stdcall;
function PolyPolygon(DC: HDC; var Points; var nPoints; p4:
Integer) : BOOL; stdcall;
function PtInRegion (RGN: HRGN; p2, p3: Integer) : BOOL; stdcall;

A continuacion, en la parte de implementacion, en lugar de ofrecer el codigo de


cada funcion, la unidad remite a la definicion externa en una DLL:
cons t
gdi32 = 'gdi32.dl11;

function PlayMetaFile; external gdi32 name 'PlayMetaFile';


function PaintRgn; external gdi32 name ' PaintRgn' ;
function PolyPolygon; external gdi32 name 'PolyPolygon';
function PtInRegion; external gdi32 name 'PtInRegion';
NOTA: En Windows.PAS se utiliza mucho la directiva {SEXTERNALSYM
i de n t i fie r ) . Esto no tiene mucho que ver con Delphi, sin0 que se apli-
-- -
c;a a
P ,
LTT
, Duuucr.
r)..:i~-- -i-t-i- --_-aparozca c~
quc
-__:A-
JXCG S ~ D V I UWIISL
-1 - I - L - ~ - n - 1 - L :
smuo~o --
u e ~ p r uGU-
rrespondiente en el archivo de cabecera traducido en C*. Eso ayuda a
mantener en sincronia 10s identificadores Delphi y C++, de tal mod0 que
ambos lenguajes puedan compartir el c6digo.

La definicion esterna de dichas funciones remite a la DLL que usan. El nom-


bre de la DLL debera incluir la extension .DLL o el programa no funcionara bajo
Windows 2000 (aunque si funcione bajo 9x). El otro elemento es el nombre de la
propia funcion de la DLL. La directiva name no es necesaria si el nombre de la
funcion (o procedimiento) Delphi se corresponde con el nombre de la funcion
DLL (que distingue entre mayusculas y minusculas).
Para llamar a una funcion que reside en una DLL, podemos ofrecer su declara-
cion y definicion externa, como se muestra anteriormente, o podemos mezclar
ambas en una unica declaracion. Una vez que la funcion se ha definido correcta-
mente, podemos llamarla en el codigo de la aplicacion Delphi, igual que con
cualquier otra funcion.

TRUCO:~elp;hiincluye la traduccih a1 lenguaje D e w de uaa $ran can-


tidad de las API de Windows, como podemas ver en bs ficheros de la

.
www .d e l p h i - j edi org.

Usar una DLL de C++


Como ejemplo, hemos escrito una DLL muy sencilla en C++, con algunas
funciones sin importancia, para ilustrar simplemente el mod0 en que hay que
llamar a las DLL desde una aplicacion Delphi. No explicaremos el codigo en C++
en detalle (se trata basicamente codigo C), en su lugar nos centraremos en las
llamadas entre la aplicacion Delphi y la DLL en C++. En la programacion con
Delphi es habitual usar bibliotecas DLL escritas en C o C++.
Supongamos que se nos proporciona una DLL creada en C o C++. Por lo
general, estaremos hablando de trabajar con un archivo .DLL (la propia bibliote-
ca compilada), un archivo .H (la declaracion de las funciones dentro de la biblio-
teca) y un archivo .LIB (otra version de la lista de las funciones exportadas para
el editor de enlaces C/C++). Este archivo LIB es totalmente inutil en Delphi,
mientras que el archivo .DLL se usa tal cual esti y el archivo .H tiene que traducirse
a una unidad en Delphi con las correspondientes declaraciones.
En el siguiente listado, podemos ver la declaracion de las funciones C++ utili-
zadas para crear el ejemplo de la biblioteca CppD11. El codigo fuente completo y
la version compilada de la DLL en C++ y el codigo fuente de la aplicacion en
Delphi que la usa estan en el directorio CppD11. Deberiamos poder compilar este
codigo con cualquier compilador C++. Veamos las declaraciones de las funciones
en C++:
extern "C" declspec (dllexport)
int WINAPI ~ o u b l e (int n) ;
extern "C" declspec(dl1export)
int WINAPI ~ r i ~ l(inte n) ;
--declspec (dllexport)
int WINAPI Add (int a, int b);

Las tres funciones realizan algunos calculos basicos sobre 10s parametros y
devuelven el resultado. Fijese en que todas las funciones se definen con el modifi-
cador W I NAP I,que define la convencion de llamada a parametros adecuada y
van precedidas de la declaracion de c 1spec ( dl lexport ) , que hace que
las funciones estkn disponibles paraprogramas externos.
Dos de estas funciones C++ utilizan tambien la convencion de nombrado de C
(indicada por la sentencia extern " c " ) ,per0 la tercera, Add,no. Esto afecta
a1 mod0 en que llamamos a estas funciones desde Delphi. De hecho, 10s nombres
internos de las tres funciones corresponden a sus nombres en el archivo de codigo
fuente en C++, a excepcion de la funcion ~ d dDado . que no hemos utilizado la
clausula extern "c" para esta funcion, el compilador C++ ha utilizado la
tecnica name mangling o de manipulacion de nombres. Se trata de una tecnica
utilizada para incluir informacion sobre el numero y tip0 de parametros en el
nombre de la funcion, que necesita el lenguaje C++ para implementar la sobrecar-
ga de funciones. El resultado a1 usar el compilador Borland C++ es un nombre de
funcion muy raro: @Add$qqsii.En realidad, este es el nombre que tenemos que
usar en nuestro ejemplo en Delphi para llamar a la funcion Add de la DLL (lo
cual explica por que tenemos que evitar normalmente la tecnica name mangling
de C++ en las funciones exportadas y por que las declaramos generalmente como
extern " c " ) .Lo que sigue son declaraciones de las tres funciones en el ejem-
plo Delphi CallCpp:
function Add (A, B: Integer) : Integer;
stdcall; external ' CPPDLL. DLL' name ' @ ~ d d $ q q s i i' ;
function Double (N: Integer) : Integer;
stdcall ; external ' CPPDLL . DLL ' name ' Double ' ;
function Triple (N: Integer) : Integer;
stdcall; external ' C P P D L L . D L L ' ;

Como podemos ver, se puede exponer u omitir el alias para la funcion externa.
Hemos ofrecido uno para la primera funcion (no habia otra alternativa, porque el
nombre de la funcion DLL exportada @Add$qqsii no es un identificador Delphi
valido) y para la segunda, aunque en el segundo caso no sea necesario. De hecho,
si 10s dos nombres se corresponden, podemos omitir la directiva name, como en
el caso de la tercera funcion anterior. Si no estamos seguros de 10s nombres reales
de las funciones exportadas por la DLL, podemos usar el programa de linea de
comandos TDump de Borland, disponible en la carpeta Delphi BIN, con el
parametro -ee.
Hay que recordar aiiadir la directiva s t d c a l l a cada definicion, por lo que el
modulo de llamada (la aplicacion) y el modulo que se va a llamar (la DLL) usan
la misma convencion para pasar parametros. De no hacerlo asi, obtendremos
valores aleatorios pasados como parametros, un error que es muy dificil rastrear.

NOTA: Cuando tenemos que convertir un archivo de cabecera grande en


C/C++ a las declaraciones correspondientes en Delphi, ~ - en lugar
- de realizar
una
--..convt
.~ - . :rsion manual podemos usar una herrarnienta para automi~ t i z aen
. ~- -~- --- r
parte el p roceso. Una de estas herrarnientas es HeadConv, escrita por Bob
Swart. PoUGIIIw3 Ae-,.n .....-.
d..h,...+..~.C
GllbW11LIQl
0-
bVpla G1l
"...&A.YaIjYla I I l 0GLI,
..l L
.--1,.1.
.rlrL#.L
UIVw~42.~~m.
Esta herramienta e s t i siendo ampIiada por Project Jedi, bajo el nombre de
proyecto DARTH (www.delphi-jedi.org/team-darth-home).Hay que tener
-- c- u- -e-n- --,
en --- - - --
- --- emharm-
t a sin -- t-----------
=--e la
m ----
n -- ------ de
r a d ~ ~ c c i hailtom6tica -- --la cahecera
-------- de -.
-- C1/
C++ a Delphi n o es posible, porque Delphi tiene un uso de tipos miis e s t r i e
to que CIC++, por lo que tenemos que usar 10s tipos de un mod0 mas
preciso.

Para usar esta DLL en C++, hemos creado un ejemplo en Delphi, denominado
CallCpp. Su sencillo formulario tiene botones para llamar a funciones de la DLL
y algunos componentes visuales para parametros de entrada y salida (vease la
figura 10.1). Fijese en que para ejecutar esta aplicacion, deberiamos tener la DLL
en el mismo directorio que el proyecto, en uno de 10s directorios de la ruta o en 10s
directorios Windows o System. Si movemos el archivo ejecutable a un nuevo
directorio e intentamos ejecutarlo, obtendremos un error en tiempo de ejecucion
indicando que no existe dicha DLL:

Creacion de una DLL en Delphi


Ademas de utilizar las DLL escritas en otros entornos, podemos usar Delphi
para crear unas DLL que puedan utilizar 10s programas Delphi o cualquier otra
herramienta de desarrollo que soporte DLL. Crear bibliotecas DLL en Delphi es
tan sencillo que podriamos hacer un uso excesivo de dicha funcion. En general,
convienc intentar crear componentes y paquctes en lugar de DLL. Como esplica-
rcmos mas adelante en este capitulo, 10s paquetes a menudo contienen componen-
tes, pero tambien puede contener clases que no son de componentes. lo que nos
pcrmitira escribir codigo orientado a objetos y reutilizarlo de un mod0 efectivo.
Los paquetes, por supuesto. tambien pueden contener rutinas, constantes. varia-
bles, etc.

Figura 10.1. La salida del ejemplo CallCpp al hacer clic en cada uno de 10s botones.

Como ya hemos dicho, es util construir una DLL cuando hay una parte del
codigo del programa sometida a frecuentes cambios. En este caso, podemos susti-
tuir frecuentemente la DLL y mantener el resto del programa igual. Asi, cuando
es necesario escribir un programa que ofrezca distintas funciones para distintos
grupos de usuarios, podemos distribuir versiones diferentes de una DLL a dichos
usuarios.

La primera DLL en Delphi


Como punto de partida a1 explorar el desarrollo de las DLL, mostraremos una
biblioteca muy sencilla creada en Delphi. El principal objetivo de este ejemplo es
mostrar la sintasis utilizada para definir una DLL, pero ilustrara tambien una
serie de cuestiones relacionadas con el paso de 10s parametros de cadena. Para
empezar, seleccionamos la orden File>New y escogemos la opcion DLL en la
ficha New del Object Repository. Esto origina un archivo fuente muy sencillo
que comienza con la siguiente definicion:
library P r o j e c t l ;

La sentencia l i b r a r y indica que queremos crear una DLL en lugar de un


archivo ejecutable. Ahora, podemos aiiadir rutinas a la biblioteca y listarlas en
una sentencia e x p o r t s :
function Triple ( N : Integer) : Integer; stdcall;
begin
try
R e s u l t : = N * 3;
except
Result := N * 1;
end;
end;

f u n c t i o n Double (N: Integer) : Integer; stdcall;


begin
Result := N * 2;
t rY
Result := N * 2;
except
Result := N * -1;
end;
end;
end;

exports
Triple, Double;

En esta version basica de la DLL, no es necesaria la sentencia u s e s , per0 en


general, el archivo principal del proyecto incluye solo la sentencia e x p o r t s ,
mientras que las declaraciones de funcion se colocan en una unidad aparte. En el
codigo fuente final del primer ejemplo FirstDLL, hemos cambiado ligeramente el
codigo respecto a la version que se muestra anteriormente, para que aparezca un
mensaje cada vez que se llama a una funcion. Hay dos formas de hacer esto. La
mas sencilla consiste en usar la unidad Dialogs y llamar a la funcion
ShowMessage.
El codigo necesita que Delphi enlace una cantidad considerable de codigo
VCL con la aplicacion. Si enlazamos estaticamente la VCL con esta DLL, el
tamaiio resultante sera de unos 37.5 KB. La razon es que la hncion ShowMessage
muestra un formulario VCL que contiene controles VCL y utiliza clases graficas
VCL. Estas remiten de forma indirecta a conceptos como el sistema de streaming
de la VCL y 10s objetos VCL aplicacion y pantalla. En el caso de este ejemplo,
una alternativa mejor consiste en mostrar 10s mensajes usando las llamadas direc-
tas de la API, utilizando la unidad Windows y llamando a la funcionMessageBox,
de tal mod0 que el codigo VCL no sea necesario. Este cambio en el codigo reduce
el tamaiio de la aplicacion a aproximadamente 40 KB.

NOTA: Esta enorme diferencia de tamaiio subraya el hecho de que no


debemos w a r en exceso las DLL en Delphi, para no compilar el cbdigo de
la VCL en diversos archivos ejecutables. Por supuesto, podemos reducir el
tamaiio de una DLL de Delphi utilizando paquetes en tiempo de ejecucibn.

Si ejecutamos un programa de prueba como el ejemplo CallFrst, que utiliza la


version de la DLL basada en API, su comportamiento no sera el adecuado. De
hecho, podemos pulsar 10s botones que llaman a las funciones DLL varias veces
sin cerrar primer0 10s cuadros de mensaje que muestra la DLL. Esto se debe a que
el primer parametro de la llamada MessageBox de la API anterior es cero. En
cambio, su valor deberia ser el manejador del formulario principal del programa o
dcl formulario de la aplicacion, informacion no disponible dentro de la propia
DLL.
Funciones sobrecargadas en las DLL de Delphi
Cuando creamos una DLL en C++, las funciones sobrecargadas utilizan la
tecnica nnme mungling para crear un nombre diferente para cada funcion, inclui-
do el tip0 parametros del nombre, como vemos en el ejemplo CppD11.
Cuando creamos una DLL en Delphi y utilizamos funciones sobrecargadas (es
decir, diversas funciones quc usan el mismo nombre y estan marcadas con la
directiva overload), Delphi pcrmite esportar una de las funciones sobrecarga-
das con el nombre original, indicando su lista de parametros en la clausula
exports. Si queremos exportar varias funciones sobrecargadas, deberiamos
especificar nombres diferentes en la clausula exports para distinguir las sobre-
cargas. Asi lo muestra esta parte del codigo de FirstDLL:
function Triple (C: Char): Integer; stdcall; overload;
function Triple (N: Integer): Integer; stdcall; overload;

exports
Triple (N: Integer),
Triple (C: Char) name ' T r i p l e c h a r ' ;

- - - -- - - - - - -

NOTA: Tambien es ~ o s i b l hacer


e lo contrario: ~ ~ d e m ioms~ o r t a una
r serie
de funciones sirnilares desde una DLL y definirlas todas como funciones
sobrecargadas en Ia declaration en Delphi. La unidad 0penGL.PAS de

Exportar cadenas de una DLL


Por lo general, las funciones de una DLL pueden usar cualquier tipo de
parametro y devolver cualquier tip0 de valor. Pero hay dos excepciones a esta
norma:
Si planeamos llamar a la DLL desde otros lenguajes de programacion,
probablemente deberiamos utilizar tipos de datos originarios de Windows
en lugar de tipos especificos de Delphi. Por ejemplo, para expresar valores
de color, deberiamos usar enteros o el tipo ColorRef de Windows en
lugar del tipo TColor originario de Delphi, realizando las conversiones
que Sean oportunas (como en el ejemplo FormDLL). Otros tipos de Delphi
que, por compatibilidad, deberiamos de evitar son, entre otros, 10s objetos,
que no pueden usar otros lenguajes y las cadenas de Delphi. que se pueden
sustituir por cadenas PChar. En otras palabras, todos 10s entornos de
desarrollo de Windows deben soportar 10s tipos basicos de la API, por lo
que si nos atenemos a ellos, nuestra DLL podra ser usada en otros entornos
de desarrollo. Ademas, las variables de archivos en Delphi (archivos de
texto y archivos binarios de registro) no se deberian pasar fuera de las
DLL, per0 podemos usar 10s manejadores de archivo de Win32.
Aunque pensemos en usar la DLL solo desde una aplicacion en Delphi, no
podemos pasar cadenas en Delphi (ni matrices dinamicas) mas alla de 10s
limites de la DLL sin tomar ciertas precauciones. Esto se debe a1 mod0 en
que Delphi administra las cadenas en memoria (asignando, reasignando y
liberando la memoria automaticamente). La solucion a1 problema consiste
en incluir la unidad de sistema ShareMem tanto en la DLL como en el
programa que la usa. Esta unidad debera incluirse como la primera de cada
proyecto. Es mas, debemos distribuir el archivo BorlndMM.DLL (Borland
Memory Manager, Administrador de memoria de Borland) junto con el
programa y la biblioteca especifica.
En el ejemplo FirstDLL, hemos incluido ambas tecnicas: una funcion recibe y
devuelve una cadena en Delphi y la otra recibe como parametro un punter0 P C h a r ,
que a continuacion rellena la propia funcion. La primera funcion es muy sencilla:
function Doublestring (S: string; Separator: Char) : string;
stdcall ;
begin
try
Result := S + Separator + S;
except
Result : = ' [ e r r o r ] ';
end ;
end :

La segunda funcion es bastante compleja porque las cadenas P C h a r no tienen


un operador + sencillo ni son directamente compatibles con caracteres. El separador
habra de convertirse en una cadena antes de aiiadirlo. Veamos el codigo completo,
que utiliza buffers P C h a r de entrada y salida, compatibles con cualquier entorno
de desarrollo Windows:
function DoublePChar (BufferIn, Bufferout: PChar;
BufferOutLen: Cardinal; Separator: Char): LongBool; stdcall;
var
SepStr: array [O. .l] of Char;
begin
try
// s i e l b u f f e r e s 1 0 s s u f i c i e n t e m e n t e a m p l i o
if BufferOutLen > StrLen (BufferIn) * 2 + 2 then
begin
// c o p i a e l b u f f e r d e e n t r a d a e n e l b u f f e r d e s a l i d a
StrCopy (BufferOut, Buf ferIn) ;
// c r e a l a cadena d e l s e p a r a d o r ( e l v a l o r mds e l
terminador nulo)
SepStr [0] : = Separator;
SepStr [I] : = #O;
// adjunta e l separador
StrCat (Bufferout, SepStr) ;
/ / a d j u n t a e l b u f f e r d e e n t r a d a u n a v e z mis
StrCat (BufferOut, Buff erIn) ;
Result : = True;
end
else
// n o hay e s p a c i o s u f i c i e n t e
Result : = False;
except
Result := False;
end ;
end;

Esta segunda version es mas compleja, per0 la primera solo se puede usar
desde Delphi. Ademas, en la primera version es necesario que incluyamos la
unidad ShareMem y que distribuyamos el archivo BorlndMM.DLL como ya se ha
explicado anteriormente.

Llamada a la DLL de Delphi


Para utilizar la biblioteca que acabamos de crear, podemos llamarla desde
cualquier otro proyecto de Delphi o desde otro entorno. Como ejemplo, hemos
creado el proyecto.CallFrst (guardado en el directorio F i r s tDLL). Para acceder
a las funciones de la DLL, debemos declararlas como external, como hemos
hecho con la DLL en C++. Sin embargo, esta vez, podemos sencillamente copiar
y pegar la definicion de las funciones desde el codigo fuente de la DLL en Delphi
y aiiadir la clausula external, como se muestra a continuacion:
f u n c t i o n Double (N: Integer): Integer;
stdcall; external ' F I R S T D L L . DLL ' ;

Esta declaracion es similar a las usadas para llamar a la DLL en C++. Esta
vez, en cambio, no hay problemas con 10s nombres de las funciones. Despues de
haber vuelto a declararlas como e x t e r n a l , las funciones de la DLL se pueden
usar como si fueran funciones locales. Veamos un ejemplo, con llamadas a las
funciones relacionadas con cadenas (la figura 10.2 muestra un ejemplo del resul-
tado) :
p r o c e d u r e TForml.BtnDoubleStringClick(Sender: TObject);
begin
// l l a m a a l a f u n c i o n d e l a DLL d i r e c t a m e n t e
EditDouble.Text : = Doublestring (EditSource.Text, I ; ' ) ;
end ;

procedure TForml.BtnDoublePCharClick(Sender: TObject);


var
Buffer: s t r i n g ;
begin
// h a c e e l b u f f e r l o s u f i c i e n t e r n e n t e a m p l i o
SetLength (Buffer, 1000) ;
/ / l l a m a a l a f u n c i o n d e l a DLL
if DoublePChar (PChar ( E d i t S o u r c e . T e x t ) , PChar (Buffer),
1000, I / ' ) t h e n
EditDouble.Text := B u f f e r ;
end;

Figura 10.2. El resultado del ejemplo CallFrst, que llama a la DLL que hernos creado
en Delphi.

Caracteristicas avanzadas de las DLL


en Delphi
Ademas de este c.jemplo introductorio, podemos hacer algunas cosas mas con
bibliotecas dinarnicas en Delphi. Podemos usar algunas nuevas directivas
de compilador que afecten a1 nombre de la biblioteca, llamar a una DLL en tiempo
de ejecucion e incluir un formulario completo dentro de una biblioteca dina-
mica.

Cambiar nombres de proyecto y de biblioteca


En el caso de una biblioteca, a1 igual que en el de una aplicacion estandar,
acabamos teniendo un nombre de biblioteca que se corresponde con un nombre de
archivo de proyecto de Delphi. Siguiendo una tecnica similar introducida en Kylis
para obtener compatibilidad con las convenciones estandar de Linux sobre deno-
minacion de bibliotecas de objetos compartidas (el equivalente en Linux de las
DLL de Windows), Delphi 6 introdujo directivas de cornpilacion especiales que
podemos usar en bibliotecas para establecer sus nombres de archivo e.jecutable.
Algunas de esas directivas tienen mas sentido en el entorno Linux quc en Windows,
pero aim asi se han aiiadido todas.
$LIBPREFIX: Se utiliza para aiiadir algo delante del nombre de la biblio-
teca. Imitando la tecnica Linux de aiiadir lib delante de 10s nombres de
biblioteca, esta directiva la usa Kylix para aiiadir bpl al principio de 10s
nombres de paquetes. Esto se debe al hecho de que Linux usa una unica
extension (.SO) para bibliotecas, mientras que en Windows podemos tener
extensiones diferentes, algo que Borland usa para 10s paquetes (.BPL).
$LIBSUFFIX: Se usa para aiiadir texto despues del nombre de la bibliote-
ca y antes de la extension. Esto se puede emplear para especificar informa-
cion sobre la version u otras variaciones del nombre de la biblioteca que
pucden ser utiles tambien en Windows.
$LIBVERSION: Se utiliza para aiiadir un numero de version tras la ex-
tension (algo muy comun en Linux, per0 que normalmente deberiamos
evitar en Windows).
Podemos fijar estas directivas en el entorno de desarrollo, en la pagina
A p p l i c a t i o n del cuadro de dialogo Project Options, como muestra la figu-
ra 10.3. Como ejemplo, consideremos las siguientes directivas, que crean una
biblioteca llamada MarcoNameTest60.dll:
library NameTest;
ISLIBPREFIX 'Mdrco I )

{SLIBSUFFIX 60 ' 1

r pel& [OK1 ~ancel I ~ c b

Figura 10.3. La pggina Application del cuadro de dialogo Project Options tiene ahora
una seccion llamada Library Name.

NOTA: Lon paquctes de Delphi 6 introdujeron el uso extensive de la direc-


tiva S L I B S U F F I X . Por esa r a d n , el paquete VCL genera 10s archivos
carnbiar las partes necesarias de nuestros paquetes para cada nueva versibn
de- Debhi. Por sunuesto. esto se transforma~~.
-
r - ~ . - ~ T en alno
~ .-" muv
.
- ~ -., c6modo
- . Dara
- --- - - -
I --- ~~

actualizar proyectos de Delphi 6 a Delphi 7, porque las versiones anteriores


de Delphi no ofrecian esta caracteristica. Cumdo abrimos paquetes de Delphi
3 aun oeoemos actualizar su coalgo ruente, una operaclon que el ~ u ace
Delphi no realiza automhticamente por nosotros.

Llamada a una funcion DLL en tiempo


de ejecucion
Hasta ahora, siempre hemos hecho referencia en el codigo a las funciones que
exportaban las bibliotecas, por lo que las DLL se cargaran junto con el programa.
Sin embargo, tambien se puede retrasar la carga de una DLL hasta el momento en
que realmente se necesite, por lo que seriamos capaces de usar el resto del progra-
ma en el caso de que la DLL no este disponible.
La carga dinamica dc una DLL cn Windows se realiza a1 llamar a la funcion
LoadLibrary de la API, que busca la DLL en el directorio del programa, en
10s directorios de la ruta y en algunos directorios del sistema. Si no se encuentra
la DLL, Windows mostrara un mensaje de error, algo que se puede evitar llaman-
do a la funcion de Delphi SafeLoadLibrary. Esta funcion time el mismo
efecto que la API que encapsula, per0 elimina el mensaje de error estandar de
Windo\vs y deberia de ser la tdcnica a la que demos preferencia para cargar las
bibliotecas de forma dinamica en Delphi.
Si se encuentra la biblioteca y se carga (algo que sabemos verificando el valor
de retorno de LoadLibrary o SafeLoadLibrary), un programa puede lla-
mar a la funcion GetProcAddress de la API, que busca la tabla de esporta-
cion de la DLL, buscando el nombre de la funcion pasada como parametro. Si
Get ProcAddress encuentra una correspondencia, devuelve un puntero a1 pro-
cedimiento solicitado. Ahora, sencillamente podemos convertir este puntero de
funcion a1 tip0 de datos apropiado y llamarlo de nuevo.
Sean cuales sean las funciones de carga utilizadas, no hay que olvidarse de
llamar a FreeLibrary a1 final, para liberar convenientemente la memoria asig-
nada a la DLL. De hecho, el sistema usa la tecnica de recuento de referencias para
bibliotecas, liberando la memoria asignada para las mismas cuando cada una de
las solicitudes de carga se ha seguido de una solicitud de liberacion de memoria.
El ejemplo creado para mostrar la carga dinamica de una DLL se denomina
DynaCall y utiliza la biblioteca FirstDLL que hemos creado antes en este mismo
capitulo (para que el programa funcione, debemos copiar la DLL en el mismo
directorio que el ejemplo DynaCall). En lugar de declarar las funciones Double
y Triple y usarlas directamente, este ejemplo consigue el mismo efecto con un
codigo en cierto mod0 mas complejo. Sin embargo, la ventaja esta en que el
programa se ejecutara incluso sin la DLL. Ademas, si se aiiaden nuevas funciones
compatibles a la DLL, no tendremos que revisar el codigo fuente del programa ni
compilarlo de nuevo para acceder a dichas funciones nuevas. Esta es la parte
central del programa:
type
TIntFunction = function (I: Integer): Integer; stdcall;

cons t
DllName = ' F i r s t d l l . d l l ' ;

procedure TForml.ButtonlClick(Sender: TObject);


var
HInst : THandle;
FPointer: TFarProc;
MyFunct: TIntFunction;
begin
HInst : = SafeLoadLibrary (DllName);
if HInst > 0 then
try
FPointer : = GetProcAddress (HInst,
PChar (Edit1.Text)) ;
if FPointer <> nil then
begin
MyFunct : = TIntFunction (FPointer);
SpinEditl-Value : = MyFunct (SpinEditl.Value);
end
else
ShowMessage ( ' f u n c i o n DLL ' + Editl.Text + ' n o
e n c o n t r a d a I) ;
finally
FreeLibrary (HInst);
end
else
ShowMessage (' b i b l i o t e c a ' + DllName + ' no
e n c o n t r a d a I) ;
end ;

usa el administrador de memoria de


nhicamente debe hacer lo mismo.
Yor ello, debemOS asiadlr la unldad shareMem en el proyecto del ejemplo
DynaCall. Evidentemente, esto no se hacia asi con versiones anteriores de
Delphi, en caso de que la biblioteca no utilizara cadenas de caracteres.
Debemos tener en cuenta que si omitimos esta inclusion, se producira un
error de sistema.

Cuando tenemos el puntero para llamar a un procedimiento en Delphi, pode-


mos convertirlo en un tip0 de procedimiento y: a continuacion, usar la variable de
tip0 de procedimiento, como en el listado anterior. Fijese en que el tip0 de proce-
dimiento que definamos habra de ser compatible con la definicion del procedi-
miento en la DLL. Este es el talon de Aquiles de este metodo, no existe verification
de tipos de parametro.
La ventaja de esta tecnica es que, en teoria, podemos usarla para acceder a
cualquier funcion de cualquier DLL en cualquier momento. En la practica, resulta
util cuando tenemos diferentes DLL con funciones compatibles o una DLL con
diversas funciones compatibles, como en este caso. Lo que podemos hacer es
llamar a 10s metodos Double y Triple introduciendo sencillamente sus nom-
bres en el cuadro de edicion. Ahora, si alguien nos proporciona una DLL con una
nueva funcion que reciba un entero como un parametro y devuelva un entero,
podemos llamarla simplemente introduciendo su nombre en el cuadro de edicion.
Ni siquiera necesitamos compilar la aplicacion de nuevo.
Con este codigo, el compilador y el editor de enlaces ignoran la existencia de la
DLL. Cuando se carga el programa, la DLL no se carga inmediatamente. Podria-
mos hacer que el programa fuese incluso mas flexible y permitir a1 usuario que
introduzca el nombre de la DLL que vamos a usar. En algunos casos, esta es una
gran ventaja. Un programa puede activar las DLL en tiempo de ejecucion, algo
que la tecnica directa no nos permite. Fijese en que esta tecnica para cargar
funciones DLL es comun en 10s lenguajes de macro y la usan muchos entornos de
programacion visual.
Solo un sistema basado en un compilador y en un editor de enlaces, como
Delphi, puede usar la tecnica directa, que por lo general es mas fiable y tambien
un poco mas rapida. La tecnica de carga indirecta del ejemplo DynaCall solo es
util en casos especiales, per0 puede resultar extremadamente potente. Por otro
lado, resulta muy valioso aprovechar la carga dinamica con paquetes que inclu-
yan formularios, como veremos hacia el final de este capitulo.

Un formulario de Delphi en una DLL


Ademas de escribir una biblioteca con funciones y procedimientos, podemos
colocar un formulario completo creado con Delphi en una DLL. ~ s t puede
e ser un
cuadro de dialogo o cualquier otro tip0 de formulario y lo pueden usar no solo
otros programas Delphi, sino tambien otros entornos de desarrollo o lenguajes de
macro que permitan usar bibliotecas de enlace dinamico. Una vez creado un nue-
vo proyecto de biblioteca, solo tenemos que aiiadir uno o mas formularios a1
proyecto y escribir las funciones exportadas que crearan y usaran esos formula-
rios.
Por ejemplo, una funcion que activa un cuadro de dialogo modal para seleccio-
nar un color puede escribirse de este modo:
function GetColor (Col: LongInt) : LongInt; cdecl;
var
FormScroll: TFormScroll;
begin
// v a l o r p r e d e f i n i d o
Result : = Col;
try
Formscroll : = TFormScroll.Create (Application);
try
// i n i c i a l i z a 1 0 s d a t o s
FormScroll.SelectedColor : = Col;
// m u e s t r a e l f o r m u l a r i o
i f FormScroll.ShowModal = mrOK then
Result : = FormScroll.SelectedColor;
finally
FormScroll.Free;
end ;
except
on E: Exception do
MessageDlg ( ' E r r o r e n l a b i b l i o t e c a : ' + E - M e s s a g e ,
mtError, [mbOK], 0) ;
end ;
end ;

Lo que hace que este codigo sea diferente del codigo que escribimos normal-
mente en un programa es el uso del control de excepciones:
Un bloque t r y / e x c e p t protege a toda la funcion, de mod0 que se atra-
para cualquier excepcion creada por la funcion, mostrando un mensaje
adecuado. La razon para controlar toda excepcion posible es que la aplica-
cion que realiza la llamada podria estar escrita en cualquier lenguaje, so-
bre todo en uno que no sepa como controlar excepciones. Aun cuando el
que llama es un programa en Delphi, a veces, resulta util la misma tecnica
de proteccion.
Un bloque t r y / fi n a l l y protege las operaciones realizadas en el for-
mulario, asegurando que el formulario se destruira correctamente, aunque
se produzca una excepcion.
A1 comprobar el valor de retorno del metodo ShowModal, el programa esta-
blece el resultado de la funcion. Hemos definido el valor predefinido antes de
introducir el bloque t r y para garantizar que siempre se va a ejecutar (y evitar
tambien la advertencia del compilador que indica que el resultado de la funcion
podria no estar definida).
Podemos encontrar este codigo en 10s proyectos FormDLL y UseCol de la
carpeta FormDLL. (Tambien encontraremos el archivo WORDCALL. TXT que
muestra como llamar a la rutina desde una macro de Word). El ejemplo muestra
tambien que podemos aiiadir un formulario no modal a la DLL, per0 esto causa
muchos problemas. El formulario no modal y el principal no se sincronizan ya
que la DLL tiene su propio objeto A p p l i c a t i o n global en su propia copia de
la VCL. Este problema puede ser parcialmente solventado copiando el H a n d l e
del o b j e t o A p p 1 i c a t i o n del programa a1 H a n d l e del o b j e t o A p p 1 i c a t i o n
de la biblioteca. No todos 10s problemas se resuelven con el codigo del ejemplo.
Una solucion mejor puede ser compilar el programa y la biblioteca para usar
paquetes Delphi, de mod0 que el codigo VCL y la inforrnacion no se dupliquen.
Pero esta tecnica causa aun algunos problemas: generalmente esta recomendado
no usar las DLL y 10s paquetes de Delphi juntos. La mejor tecnica para hacer que
10s formularios de una biblioteca sean accesibles para otros programas Delphi es
usar paquetes en lugar de las DLL.

Bibliotecas en memoria: codigo y datos


Antes de hablar de 10s paquetes, vamos a centrarnos en un elemento tecnico de
las bibliotecas dinamicas: como usan la memoria. Empecemos con la porcion de
codigo de la biblioteca, despues nos centraremos en sus datos globales. Cuando
Windows carga el codigo de una biblioteca, como cualquier otro modulo de codi-
go, tiene que ejecutar una operacion de parcheo. Este parcheo consiste en corregir
las direcciones de 10s saltos y llamadas a funciones internas con las direcciones
reales de memoria donde han sido cargadas.
Esto no es un problema para 10s ficheros ejecutables, per0 puede causar pro-
blemas con las bibliotecas. Si dos ejecutables cargan la misma biblioteca en la
misma direccion base, habra una unica copia fisica del codigo de la DLL en la
RAM (memoria fisica) de la maquina, para asi ahorrar espacio de memoria. Si la
segunda vez que la biblioteca se carga la direccion de memoria esta ya en uso,
necesita ser reubicada, es decir, trasladada con un parcheo diferente. Por lo que,
a1 final, tendremos dos copias fisicas del codigo de la DLL en la RAM.
Podemos usar la tecnica de carga dinamica, basada en la funcion
G e t ProcAddress de la API, para comprobar a que direccion de memoria del
proceso en uso se ha proyectado una funcion, mediante el siguiente codigo:
procedure TForml.Button3Click(Sender: TObject);
var
HDLLInst: THandle;
begin
HDLLInst : = SafeLoadLibrary ( 'dllmem') ;
Labell.Caption : = Format ('Address: % p ' , [GetProcAddress
(HDLLInst, ' Se tDa t a ' ) ] ) ;
FreeLibrary (HDLLInst);
end:

Este codigo muestra, en una etiqueta, la direccion de memoria de la funcion, en


el espacio de direcciones de la aplicacion que llama. Si ejecutamos dos programas
que usen este codigo, por lo general, ambos mostraran la misma direccion. Esto
demuestra que el codigo se carga solo una vez en una direccion de memoria
comun.
Otra manera de conseguir mas informacion acerca de lo que esta ocurriendo es
usar la ventana Modules de Windows, que muestra la direccion base de cada
biblioteca referenciada por el modulo y la direccion de cada funcion dentro de la
biblioteca, como se muestra aqui:

N m e . _ .-- . ~ - B a s s ! W - - P h -

GDl32 dl $77C40OO0 C \WlNDlOWS\syslern32\G


ADVAPI32 dl $77DAOWO C \WlNDOWS\syslern32\A
RPCRT4 dl1 $77C90000 C \WINDOWS\syslern32W

1
OLEAUT32 dl $770F0000 C \WINDOWS\syslem32\oI
rnsvul dl1 $77BE0000 C\WlNDOWS\syslern32\M
de32 dl1 $771800W C \WINDOWS\syslern3nO
VERSION dll $77BD0000 C \WINDOWS\sydern32\v
CDMCTL32 dY 977310000 C.\WINDOWS\syslem32\c
bhmndl -W m W - - D:\md7de\lO\DMm\~
MSCTF dl $74680000 C \WINDOWS\syslern32\M
TabHook dR $10000WO C \WINDOWS\Syslern32\I
UxTherne dl1 $58150000 C \WINDOWS\Syslern32\u
MOUSEDLL dl1 $00780000 C W~chwosde progma\Br

Es importante saber que la direccion base de una DLL es algo que podemos
solicitar activando la opcion de direccion base. En Delphi esta direccion se deter-
mina mediante el valor Image Base de la pagina del editor de enlaces del cuadro
de dialog0 Project Options. En la biblioteca DllMem, por ejemplo, esta puesta
en $ 0 0 8 0 0 0 0 0 . Necesitamos tener un valor diferente para cada una de nuestras
bibliotecas, asegurandonos de que no entra en conflict0 con ninguna biblioteca
del sistema u otra biblioteca (paquete, control ActiveX y otros) usada por el
ejecutable. Esto tambien puede verificarse mediante la ventana Module del depu-
rador .
A pesar de que esto no garantiza una ubicacion unica, fijar una direccion base
para una biblioteca es siempre mejor que no hacerlo; en este caso siempre ocurre
una reubicacion, pero la probabilidad de que dos ejecutables diferentes reubiquen
la misma biblioteca en la misma direccion no es muy alta.

NOTX: Podemos utilizar Procep E ~ l o r e rde h tt p : / f w w w . '


sys$nternals. corn para e x a d n a r walquier proceso eri cualquier
*ha. Esta hcrr;imientat i m e incEtlso ma opcion $ara resaltar las DLL
reubi~adas.

Si se carga el codigo de la DLL solo una vez, podriamos preguntarnos que


ocurre con 10s datos globales. Basicamente, cada copia de la DLL tiene su propia
copia de 10s datos, en el espacio de direcciones de la aplicacion que llama. Sin
embargo, posiblemente sera necesario compartir datos globales entre las aplica-
ciones que usen una DLL. La tecnica mas comun para compartir datos consiste en
usar archivos proyectados en memoria. Usaremos esa tecnica en el caso de una
DLL, pero podemos tambien utilizarla para compartir directamente 10s datos en-
tre las aplicaciones.
Este ejemplo se llama DllMem e incluye el proyecto DllMem (la propia DLL)
y el proyecto UseMem (la aplicacion demo). El codigo DLL tiene un archivo de
proyecto que exporta cuatro subrutinas:
l i b r a r y dllmem;

uses
SysUtils,
DllMemU i n ' DllMemU. p a s ' ;
exports
S e t D a t a , GetData,
GetShareData, S e t S h a r e D a t a ;
end.

El codigo real esta en la unidad secundaria (DllMemU. PAS), que tiene el


codigo de las cuatro rutinas que leen o escriben dos posiciones de memoria globales.
st as contienen un entero y un puntero a un entero. Veamos las declaraciones de
variables y las dos rutinas set:
var
PlainData: I n t e g e r = 0 ; // no compartido
S h a r e D a t a : " I n t e g e r ; // c o m p a r t i d o

p r o c e d u r e SetData ( I : Integer); stdcall;


begin
PlainData := I ;
end;

p r o c e d u r e SetShareData ( I: Integer) ; s t d c a l l ;
begin
ShareData" : = I ;
end;

Compartir datos con archivos proyectados


en memoria
En el caso de 10s datos no compartidos, no podemos hacer nada mas. Sin
embargo, para acceder a datos compartidos, la DLL tiene que crear un archivo
proyectado en memoria y, a continuacion, obtener un puntero a esa zona de
memoria. Estas dos operaciones requieren dos llamadas a la API de Windows:
CreateFileMapping: Requiere como parametros el nombre de archivo (o
$ F F F F F F F F para usar un archivo virtual en memoria), algunos atributos
de seguridad y proteccion, el tamaiio de 10s datos y un nombre interno (que
habra de ser el mismo para compartir el archivo proyectado desde diversas
aplicaciones que lo llamen).
MapViewOfFile: Requiere como parametros el manejador del archivo pro-
yectado en memoria, algunos atributos y desplazamientos y el tamaiio de
10s datos (de nuevo).
Aqui vemos el codigo de la parte de inicializacion, ejecutado cada vez que se
cargan las DLL en un nuevo espacio de procesado (es decir, una vez para cada
aplicacion que usa la DLL):
var
hMapFile: THandle;

cons t
VirtualFileName = ' S h a r e D 1 l D a t a l ;
DataSize = sizeof (Integer);

initialization
// crea un archivo proyectado e n memoria
hMapFile : = CreateFileMapping (SFFFFFFFF, nil,
Page-Readwrite, 0, DataSize, VirtualFileName);
if hMapFile = 0 then
raise E x c e p t i o n - C r e a t e ('Error creando archivo proyectado
e n memoria ' ) ;

// obtiene el p u n t e r o a 10s datos reales


ShareData : = MapViewOfFile ( hMapFile, ~ile-Map-Write, 0, 0,
DataSize) ;

Cuando termina la aplicacion y se libera la memoria asignada a la DLL, hay


que liberar el puntero a1 archivo proyectado y el propio archivo proyectado:
finalization
UnmapViewOfFile (ShareData);
CloseHandle (hMapFile);

El formulario de esta aplicacion tiene cuatro cuadros de edicion (dos con un


control UpDown conectado), cinco botones y una etiqueta. El primer boton guar-
da el valor del primer cuadro de edicion en 10s datos de la DLL, obteniendo el
valor del control conectado UpDown:
SetData (UpDownl.Position);

Si hacemos clic sobre el segundo boton, el programa copia 10s datos de la DLL
en el segundo cuadro de edicion:
Edit2 .Text := IntToStr (GetData);

El tercer boton se usa para mostrar las direcciones de memoria de una funcion,
con el codigo fuente que aparece a1 principio de este apartado. Los ultimos dos
botones tienen basicamente el mismo codigo que 10s dos primeros, per0 llaman a1
procedimiento Set ShareData y a la funcion Get ShareData. Si ejecutamos
dos copias de este programa, podemos ver que cada copia tiene su propio valor
para 10s datos globales normales de la DLL, mientras 10s valores de 10s datos
compartidos son comunes. Hay que definir valores diferentes en 10s dos progra-
mas y despues obtener ambos para ver el efecto. Esta situacion se muestra en la
figura 10.4.

ADVERTENCIA: Los archivos proyectados en memoria reservan un ran-


go minim0 de 64 KB de direcciones virtuales y consumen memoria fisica en
paginas de 4 KB.El uso en el ejemplo de datos Integer de cuatro bytes en
memoria compartida resulta bastante costoso, sobre toda si usamos la mis-
ma t6cnica compartir diversos valores. Si necesitamos compartir di-
versas variables, deberiamos colocarlas todas en una unica zona de memoria
n
b un lm
r .rry. x
a& I.. annnnrtnr
d ;nduna \y bububr an Ina A;ot;m+nr .rnm4nLlno mmonrrdn . . r . . m t n r n o r\ n m n . r d n
laa u r a ~ r u ~ av aa 1 r a v 1 ~ aUJLLUUU ~ U U L G L U D v W~LLUUU

una estructura de registro para todas).

Figura 10.4. Si ejecutarnos dos copias del prograrna UseMern, verernos que 10s
datos globales de su DLL no son cornpartidos.

Uso de paquetes Delphi


En Delphi, 10s paquetes de componentes son un tipo de DLL importante. Los
paquetes permiten agrupar componentes y7 a continuation, enlazarlos estatica
(aiiadiendo su codigo compilado a1 archivo ejecutable de la aplicacion) o
dinamicamente (guardando el codigo del componente en una DLL, el paquete en
tiempo de ejecucion que se distribuira junto con el programa, junto con el resto de
paquetes necesarios). Veremos ahora algunas ventajas y desventajas de las dos
formas de enlace de un paquete. Esisten muchos elementos que debemos tener en
cuenta:
Usar un paquete como una DLL reduce el tamaiio de 10s archivos ejecutables.
Enlazar las unidades del paquete con el programa permite distribuir solo
parte del codigo del paquete. El tamaiio del archivo ejecutable de una apli-
cacion mas el tamaiio del paquete DLL requerido es siempre mucho mayor
que el tamaiio del programa enlazado estaticamente. El editor de enlaces
incluye solo el codigo realmente utilizado por el programa, mientras que
un paquete habra de enlazar todas las funciones y clases declaradas en las
partes de interfaz de todas las unidades que contiene el paquete.
Si distribuimos varias aplicaciones Delphi basadas en 10s mismos paque-
tes, podriamos acabar distribuyendo menos codigo, porque 10s paquetes en
tiempo de ejecucion son compartidos. En otras palabras, una vez que 10s
usuarios de la aplicacion tengan 10s paquetes de tiempo de ejecucion estandar
de Delphi, podremos proporcionarles programas muy pequeiios.
Si ejecutamos varias aplicaciones Delphi basadas en 10s mismos paquetes,
podemos ahorrar algun espacio en memoria en tiempo de ejecucion. El
codigo de 10s paquetes en tiempo de ejecucion se carga en memoria solo
una vez entre las diversas aplicaciones.
No conviene preocuparse demasiado por distribuir un archivo ejecutable
amplio. Tengamos en cuenta que cuando realizamos pequeiios cambios en
un programa, podemos utilizar las diversas herramientas para crear un
archivo parche, de mod0 que distribuimos solo un archivo que contenga las
diferencias, no una copia completa de 10s archivos.
Si colocamos algunos formularios de nuestro programa en un paquete en
tiempo de ejecucion, podemos compartirlos entre varios programas. Sin
embargo, cuando modificamos estos formularios, normalmente sera nece-
sario volver a compilar tambien el programa principal y distribuir de nue-
vo ambos programas a 10s usuarios.
Un paquete es una coleccion de unidades compiladas (incluyendo clases,
tipos, variables, rutinas), que no difieren en absoluto de las unidades de
dentro del programa. La unica diferencia esta en el proceso de construc-
cion. El codigo de las unidades del paquete y el de las unidades del progra-
ma principal que las usa es identico. Esta es una de las ventajas de 10s
paquetes respecto a las DLL.

Versiones de paquetes
Un elemento muy importante y normalmente incomprendido es la distribucion
de paquetes actualizados. Cuando actualizamos una DLL, podemos incluir la
nueva version y 10s programas ejecutables que necesiten dicha DLL todavia fun-
cionaran (a menos que hayamos eliminado las funciones exportadas previamente
existentes o cambiado algunos de sus parametros).
Sin embargo, cuando distribuimos un paquete Delphi, si actualizamos el pa-
quete y modificamos la parte de interfaz de cualquier unidad del paquete, podria
ser necesario compilar de nuevo todas las aplicaciones que usen el paquete. Esto
es necesario si aiiadimos metodos o propiedades a una clase, per0 no si aiiadimos
nuevos simbolos globales (o modificamos algo no utilizado por aplicaciones de
cliente). Con respecto a 10s cambios que afecten solo a la seccion de implernentacion
de la unidad de paquete no existe problema alguno.
Un archivo DCU en Delphi posee un indicador de version basado en su sello de
informacion sobre la ultima actualizacion y en una suma de verificacion, calcula-
dos desde la parte de interfaz de la unidad. Cuando cambiamos la parte de interfaz
de una unidad, todas las demas unidades basadas en ella deberian compilarse de
nuevo. El compilador compara el sello de informacion sobre la ultima actualiza-
cion y la suma de verificacion de la unidad de compilaciones previas con el nuevo
sello de informacion sobre la ultima actualizacion y la nueva suma de verificacion
y decide si la unidad dependiente ha de ser compilada de nuevo. Esa es la razon
por la que debemos compilar de nuevo cada unidad cuando conseguimos una
nueva version de Delphi que ha modificado unidades de sistema.
En Delphi 3 (cuando se introdujeron por primera vez 10s paquetes), se aiiadio
una suma de verificacion del paquete, como funcion de entrada adicional a la
biblioteca de paquetes, obtenida a partir de la suma de verificacion de las unida-
des contenidas y la suma de verificacion de 10s paquetes necesitados. Esta suma
de verificacion era posteriormente llamada por 10s programas que usaban el pa-
quete de mod0 que cualquier ejecutable basado en una version antigua no arran-
caria. Delphi 4 y las versiones siguientes hasta Delphi 7 han disminuido las
restricciones en tiempo de ejecucion del paquete. Sin embargo, las restricciones
en tiempo de diseiio en archivos DCU siguen siendo identicas. La suma de verifi-
cation de 10s paquetes ya no se comprueba, por lo que podemos modificar direc-
tamente las unidades que forman parte de un paquete y desplegar una nueva
version del paquete que se va a usar con el archivo ejecutable existente. Dado que
nos referimos a 10s metodos por nombre, no podemos eliminar ningun metodo
existente. No podemos ni siquiera cambiar sus parametros, debido a las tecnicas
name mangling aiiadidas especificamente a 10s paquetes para protegerlos contra
cambios en sus parametros.
Eliminar un metodo referenciado desde un programa lo detendra durante el
proceso de carga. Si hacemos otros cambios el programa puede fallar inesperada-
mente durante la ejecucion. Por ejemplo, si sustituimos un componente de un
formulario compilado en un paquete por un componente similar, el programa que
hace la llamada puede ser capaz aun de acceder a1 componente en esa posicion de
memoria, aunque ahora sea diferente. Si decidimos cambiar la interfaz de las
unidades de un paquete sin recompilar todos 10s programas que las usan, deberia-
mos limitar 10s cambios. A1 aiiadir propiedades nuevas o metodos no virtuales a1
formulario, deberiamos ser capaces de mantener la compatibilidad total con 10s
programas que ya usan el paquete. Ademas, aiiadir campos y metodos virtuales
puede afectar a la estructura interna de la clase, derivando en problemas con
programas existentes, que esperan una informacion de clase y un formato de
tablas de metodos virtuales (VMT) diferentes.
ADVERTENCIA: Esto se refiere la distribuci6n en programas cornpila-
dos divididos en EXE y paquetes, no a la distribuci6n dr: componentes a
otros desarrolladores en Delphi. En este ultimo caso, las normas sobre
versiones son m b estrictas y deberiamos prestar una atencibn especial a
las versiones de 10s paquetes.

Dicho esto: es recomendable no cambiar nunca la interfaz de ninguna unidad


exportada por nuestros paquetes. Para ello, podemos aiiadir a nuestro paquete
una unidad con funciones de creacion de formularios (como en las DLL con
formularios presentadas previamente) y utilizarla para acceder a otra unidad que
defina 10s formularios. A pesar de que no hay manera de ocultar una unidad que
esta enlazada a un paquete, si nunca usamos directamente la clase definida en una
unidad, sin0 que la usamos a traves de otras rutinas, conseguiremos una mayor
flexibilidad para modificarla. Tambien podemos usar la herencia de formularios
para modificar un formulario que este dentro de un paquete sin afectar a la ver-
sion original.
La regla mas restrictiva con respecto a 10s paquetes, usada por 10s autores de
componentes, es esta: para una distribucion y mantenimiento a largo plazo del
codigo de 10s paquetes, debemos planificar realizar una distribucion principal de
mayor entidad con distribuciones menores de mantenimiento. Una distribucion de
gran tamaiio de un paquete requerira recompilar todos 10s programas cliente; el
fichero del paquete debera entonces ser renombrado de acuerdo con un nuevo
numero de version, y las secciones de interfaz de las unidades podran ser modifi-
cadas. Las distribuciones de mantenimiento de ese paquete deberian limitarse a
cambios de implernentacion para mantener la compatibilidad total con 10s
ejecutables y las unidades existentes, tal y como hace Borland con sus Update
Packs.

Formularios dentro de paquetes


En capitulos previos se ha comentado el uso de paquetes de componentes en
las aplicaciones Delphi. Ahora se planteara el uso de 10s paquetes y de las DLL
para dividir una aplicacion, por lo que empezaremos hablando del desarrollo de
paquetes que contengan formularios. Como hemos visto, podemos usar formula-
rios dentro de las DLL, pero, a veces, esto origina problemas. Si estamos creando
la biblioteca y el archivo ejecutable en Delphi, el uso de paquetes es una solucion
mucho mejor y mas clara.
A primera vista, podriamos creer que 10s paquetes Delphi son un mod0 de
distribuir componentes que se van a instalar en el entorno. En cambio, podemos
usar paquetes para estructurar el codigo que, a diferencia de las DLL, conservara
toda la potencia de la programacion orientada a objetos de Delphi. Tengamos en
cuenta que un paquete es una coleccion de unidades compiladas y que nuestro
programa usa varias unidades. Las unidades referenciadas por el programa se
recompilaran dentro del archivo ejecutable, a no ser que especifiquemos a Delphi
que 10s coloque dentro del paquete. Como hemos dicho anteriormente, esta es una
de las razones principales para usar paquetes.
Para definir una aplicacion de tal mod0 que su codigo este dividido entre uno o
mas paquetes y un archivo ejecutable principal, solo hay que compilar algunas de
las unidades en un paquete y, a continuacion, configurar las opciones del progra-
ma principal para enlazar dinamicamente ese paquete. Por ejemplo, hemos hecho
una copia del formulario de seleccion de color "habitual" y hemos llamado a su
unidad PackScrollF, despues hemos creado un nuevo paquete y lo hemos aiiadido
a la unidad, como muestra la figura 10.5.

Figura 10.5. Estructura de un paquete que contiene un formulario en el Package


Editor de Delphi.

Antes de compilar este paquete, deberiamos cambiar sus directorios de salida


predefinidos para remitir al directorio actual, no al subdirectorio estandar /
Projects / ~ p de l Delphi. Para ello, vamos a la ficha Directories/Conditional
de las Project Options del paquete y definimos el directorio actual (un solo
punto, abreviado) como directorio Output (para la BPL) y directorio de salida
DCP. DespuCs compilamos el paquete y no lo instalamos en Delphi, no hace falta.
Ahora, podemos crear una aplicacion normal y escribir el codigo estandar que
usaremos en un programa para mostrar un formulario secundario, como en el
siguiente listado:
uses
PackScrollF;

procedure TForml.BtnChangeClick(Sender: TObject);


var
FormScroll: TFormScroll;
begin
FormScroll := TFormScroll .Create (Application);
try
// inicializa 10s datos
FormScroll.SelectedColor : = Color;
// muestra el formulario
i f FormScroll.ShowModal = mrOK t h e n
C o l o r : = FormScroll.SelectedColor;
finally
FormScroll.Free;
end;
end ;

procedure TForml.BtnSelectClick(Sender: TObject);


var
FormScroll: TFormScroll;
begin
FormScroll : = TFormScroll .Create (Application);
// inicia 10s datos y la IU
FormScroll.SelectedColor : = Color;
FormScroll.BitBtnl.Caption := 'Apply';
FormScroll.BitBtnl.0nClick : = FormScroll.ApplyClick;
FormScroll.BitBtn2.Kind : = bkclose;
/ / muestra el formulario
FormScroll.Show;
end ;

Una de las ventajas de esta tecnica esta en que podemos referirnos a un formu-
lario compilado en un paquete con un codigo esactamente igual a1 que usaremos
para un formulario compilado en el programa. En realidad, si compilamos este
programa, la unidad del formulario se enlazara a el. Para guardarlo en el paquete,
tenemos que usar paquetes en tiempo de ejecucion para la aplicacion y aiiadir de
forma manual el paquete PackWithForm a la lista de paquetes en tiempo de ejecu-
cion (esto no lo sugiere el IDE de Delphi porque no hemos instalado el paquete en
el entorno de desarrollo).
Despues de esto, compilamos el programa y se comportara como es habitual.
Pero ahora el formulario esta en un paquete DLL e incluso se puede modificar el
formulario en el paquete, compilarlo de nuevo y ejecutar sencillamente la aplica-
cion para ver 10s efectos. Sin embargo, fijese en que con la mayor parte de 10s
cambios que afectan a la parte de interfaz de las unidades del paquete, deberiamos
compilar tambien de nuevo el programa ejecutable que llama a1 paquete.

NOTA: Podemos encontrar el paquete y el programa de prueba en la carpe-


t . PackForm en la que estA el d d i g o fuente relativo a este capitulo. El
_ J - 1 -:-..:- . - 1-
-13:.
coalgo _:_...-I_
--*L
ael slgulenre ejemplo esra
. I _
en la rmsma carpaa. FEIl paqueze -y 10s
I - I--

proyectos son todos referenciados por el archivo & grupo de proyecfo (BPG)
de la carpeta.

Carga de paquetes en tiempo de ejecucion


En el ejemplo anterior, hemos indicado que el paquete PackWithForm es un
paquete en tiempo de ejecucion que va a utilizar la aplicacion. Esto significa que
el paquete es necesario para ejecutar la aplicacion y que se carga cuando arranca
el programa. Estos dos aspectos se pueden evitar cargando el paquete de forma
dinamica, como hemos hecho con las DLL. El programa resultante sera mas
flexible, arrancara mas rapido y usara menos memoria.
Es importante tener en cuenta que habra que llamar a las funciones
L o a d P a c k a g e y U n l o a d P a c k a g e de Delphi en lugar de a l a s funciones de la
API de Windows L o a d L i b r a r y o S a f e L o a d L i b r a r y y F r e e L i b r a r y .
La diferencia esta en que las funciones de Delphi cargan 10s paquetes y tambien
llaman a su codigo de iniciacion y finalizacion correspondientes.
Ademas de este importante aspecto, sera necesario algun codigo adicional en
el programa, puesto que no podemos hacer referencia desde el programa principal
a la unidad que alberga el formulario.
No podemos usar la clase de formulario directamente, ni acceder a sus propie-
dades ni componentes. Por lo menos, no podemos hacerlo con el codigo estandar
de Delphi. Sin embargo, ambos problemas se pueden resolver utilizando referen-
cias de clase, registro de clases y RTTI (informacion de tipos en tiempo de ejecu-
cion) .
En la unidad del formulario, en el paquete, hemos aiiadido este codigo de
inicializacion:
initialization
Registerclass (TFormScroll);

Cuando se carga el paquete, el programa principal puede usar la funcion de


Delphi G e t C l a s s para obtener la referencia de clase de la clase registrada y, a
continuacion, llamar a1 constructor c r e a t e para dicha referencia de clase.
Para resolver el segundo problema, hemos definido la propiedad s e l e c -
t e d C o l o r del formulario en el paquete como una propiedad publicada, por lo
que esta accesible mediante la RTTI. A continuacion, hemos reemplazado el codi-
go que accede a esta propiedad ( F o r m S c r o l l .C o l o r ) con el siguiente codigo:
SetPropValue (FormScroll, ' S e l e c t e d C o l o r l , Color);

Para resumir estos cambios, veamos el codigo utilizado por el programa prin-
cipal (la aplicacion DynaPackForm) para mostrar el formulario modal desde el
paquete cargado dinamicamente:
procedure TForml.BtnChangeClick(Sender: TObject);
var
FormScroll: TForm;
FormClass: TFormClass;
HandlePack: HModule;
begin
// intenta cargar e l paquete
HandlePack := LoadPackage ( ' PackWi thForm. bpl ) ;
if HandlePack > 0 then
begin
FormClass := TFormClass (Getclass ( ' TFormScroll' ) ) ;
if Assigned (FormClass) then
begin
FormScroll : = FormClass .Create (Application);
try
// inicia 10s datos
SetPropValue (FormScroll, ' SelectedColor' , Color) ;
// muestra el formulario
if FormScroll.ShowModal = mrOK then
Color : = GetPropValue (FormScroll, lSelectedColor');
finally
FormScroll.Free;
end;
end
else
ShowMessage ('Form class not found');
UnloadPackage (Handlepack);
end
else
ShowMessage ( I Package not found1);
end;

Cabe destacar que el programa descarga el paquete tan pronto como ha acaba-
do con el. Este paso no es obligatorio. Podiamos haber movido la llamada
U n l o a d P a c k a g e a1 manejador O n D e s t r o y del formulario para asi evitar la
recarga del paquete despues de la primera Ilamada.
Ahora podemos ejecutar el programa sin que el paquete este disponible y vere-
mos que arranca de forma adecuada, solo indicara que no puede encontrar el
paquete cuando pulsemos el boton Change. En este programa no es necesario
utilizar paquetes en tiempo de ejecucion para mantener la unidad fuera del archi-
vo ejecutable porque no se referencia a la unidad desde el codigo. Ademas, no es
necesario que el paquete PackWithForm este en la lista de paquetes en tiempo de
ejecucion.
De todos modos, debemos utilizar paquetes en tiempo de ejecucion o el progra-
ma incluira las variables globales VCL (como el objeto A p p l i c a t i o n ) y el
paquete cargado de forma dinamica contendra otra version, porque se referira de
todos modos a 10s paquetes VCL.

mente;se ciefrttpcklmoa sufrir violaciones de acceso. Frecuentemente, es-


: .tas ocurren porqw y&. abjet:o cuya clase esta definida en el paquete se
mantiene en memoria incluso cuando el paquete es descargado de la memo-
'
ria.
~. Cuando
. el nr r "o -e- -hv -se
- - - -
- c
-.,
- - -i I*.
&..-A*
~ . ~, uucuc
= ~~
-..--..l:1
:-&--*-*
muznr*l
... UUGI
.--
*..- 1-.-
-L:-b-
_ .., . . I I ~ I I U -
- - uulcru
a 1-cac --- - - - -~----

: do a1 rnaodo D e s t r o y de una VMT inexistente, y causar por ello un


error. Dado que este tipi de errores son muy dificiles de detect& y corregir
. roaos - mres ae aescargar
I 1 . .
aeoemos asegurarnos
- . - 3 3
a e aesrrulr -. J 1
ros oojnos 1 A . L - - .t-1.
er
- - - - -1
I
Uso de interfaces en paquetes
Acceder a las clases de 10s formularios mediante 10s metodos y las propiedades
resulta mucho mas sencillo que utilizar siempre la RTTI. Para crear una aplica-
cion mas amplia, intentaremos usar interfaces y tener varios formularios que
implementaran cada uno una serie de interfaces estandar definidas por el progra-
ma. Un ejemplo no puede realmente mostrar este tipo de arquitectura ya que esta
se vuelve relevante cuando se trata de un programa grande, per0 hemos intentado
crear un programa que muestre como puede aplicarse esta idea en la practica.
Para crear la arquitectura del proyecto IntfPack, hemos utilizado tres paquetes
mas una aplicacion de muestra. Dos de estos tres paquetes (denominados
IntfFormPack e IntfFormPack2) definen formularios alternativos utilizados para
seleccionar un color.
El tercer paquete (llamado IntfPack) contiene una unidad compartida, utiliza-
da por 10s otros dos paquetes. Esta unidad engloba basicamente la definicion de la
interfaz. No es posible aiiadirla a 10s otros dos paquetes porque no se pueden
cargar dos paquetes que contienen el mismo nombre de unidad (aunque se carguen
en tiempo de ejecucion).
El unico archivo del paquete IntfPack es la unidad IntfColSel, que aparece en
el listado 10.1. Esta unidad define la interfaz comun mas una lista de clases
registradas, que imitan la tecnica de Delphi Registerclass, per0 pone a
nuestra disposicion la lista completa para que podamos recorrerla facilmente.

Listado 10.1. La unidad IntfColSel del paquete lntfPack

unit IntfColSel:

interface

uses
Graphics, Contnrs;

type
IColorSelect = interface
[ ' {3F96l395-7lF6-48ZZ-BDO2-3B475PF516D4} ' ]
function Display (Modal: Boolean = True) : Boolean;
procedure SetSelColor (Col: TColor);
function GetSelColor: TColor;
property SelColor: TColor
read GetSelColor write SetSelColor;
end;

procedure RegisterColorSelect (AClass: TClass);

var
ClassesColorSelect: TClassList;

implementation
procedure RegisterColorSelect (AClass: TClass);
begin
i f ClassesColorSelect. IndexOf (AClass) < 0 then
ClassesColorSelect.Add (AClass);
end;

initialization
ClassesColorSelect := ~~1assList.Create;

finalization
ClassesColorSelect.Free;
end.

Cuando ya tenemos esta interfaz, podemos definir formularios que la


implementen, como en el siguiente ejemplo de IntfFormPack:
type
TFormSimpleColor = class (TForm, IColorSelect)
...
private
procedure SetSelColor (Col: TColor) ;
function GetSelColor: TColor;
public
function Display (Modal: Boolean = True) : Boolean;

Los dos metodos de acceso leen y escriben sencillamente el color de algunos


componentes del formulario (un control ColorGrid en este caso especifico), mien-
tras el metodo Display llama internamente a Show o ShowModal,depen-
diendo del parametro:
function TFormSimpleColor.Display(Modal: Boolean): Boolean;
begin
Result : = True; // predefinido
if Modal then
Result : = (ShowModal = mrOK)
else
begin
BitBtnl.Caption : = 'Apply';
BitBtnl-OnClick : = Applyclick;
BitBtn2.Kind : = bkClose;
Show;
end ;
end;

Como puede verse en este codigo, cuando el formulario es no modal el boton


OK se convierte en un boton Apply. Por ultimo, la unidad tiene el codigo de
registro en la parte de inicializacion, para ejecutarlo cuando se carga el paquete
dinamicamente:
RegisterColorSelect (TFormSimpleColor);

El segundo paquete, IntFormPack2, tiene una arquitectura similar per0 un


formulario diferente como podemos ver en su codigo fuente.
Con esta arquitectura, podemos crear un programa mas flexible y elegante,
basado en un solo formulario. Cuando se crea el formulario, define una lista de
paquetes (denominada Handles Packages) y 10s carga todos.
Por ultimo, despues de cargar 10s paquetes, el programa muestra las clases
registradas en un cuadro de lista. Este es el codigo de 10s metodos
LoadDynaPackage y Formcreate:
procedure TFormUseIntf.FormCreate(Sender: TObject);
var
I: Integer;
begin
// carga t o d o s 1 0 s paquetes e n tiempo de e j e c u c i o n
Handlespackages : = TList.Create;
LoadDynaPackage ( ' IntfFormPack. b p l ' ) ;
LoadDynaPackage ( ' IntfFormPack2. b p l ' ) ;

// adade 10s nombres de c l a s e y s e l e c c i o n a e l primer0


f o r I : = 0 t o ClassesColorSelect.Count - 1 do
1bClasses.Items.Add (ClassesColorSelect [I].ClassName);
IbClasses. ItemIndex := 0;
end;

procedure TFormUseIntf.LoadDynaPackage(PackageName: string);


var
Handle : HModule ;
begin
// i n t e n t a cargar e l paquete
Handle : = Loadpackage (PackageName) ;
i f Handle > 0 then
// adade a l a l i s t a para e l i m i n a r m ' s t a r d e
HandlesPackages.Add (Pointer(Hand1e))
else
ShowMessage ( ' P a c k a g e ' + PackageName + ' n o t f o u n d ' ) ;
end;

La razon principal para mantener la lista de controladores de paquete es poder


descargarlos todos cuando termina el programa. De hecho, no necesitamos estos
controladores para acceder a 10s formularios definidos en dichos paquetes. El
codigo en tiempo de ejecucion utilizado para crear y mostrar un formulario utiliza
simplemente las clases de componentes correspondientes. Este es un extract0 de
codigo utilizado para mostrar un formulario no modal (una opcion controlada por
una casilla de verificacion):
var
AComponent : TComponent ;
ColorSelect: IColorSelect;
begin
AComponent : = TComponentClass
(ClassesColorSelect[LbClasses.ItemIndex]) .Create
( A p p l i c a t i o n );
ColorSelect : = AComponent a s IColorSelect;
ColorSelect.SelColor : = Color;
ColorSelect.Display ( F a l s e ) ;

El programa usa en realidad la funcion supports para verificar que el for-


mulario soporta la interfaz antes de usarla y tambien cuenta con la version modal
del formulario; per0 su esencia esta presente en las cuatro instrucciones anterio-
res. Ademas, hay que destacar que el codigo no necesita un formulario. Un buen
ejercicio podria consistir en aiiadir a la arquitectura un paquete con un compo-
nente que encapsulara o heredara del cuadro de dialog0 de selection de color

ADVERTENCIA: El programa principal se refiere a la unidad que aIoja


la definition de la interfaz Dero no deberia enlazarse a este archivo. En
Iugar de eso, deberia usarse el paquete en tiempo de ejecucion que contiene
esta unidad, como ocurre en el caso de 10s paquetes cargados dinhicamente.
De no ser asi, el programa principal utilizara m a copia diferente del mismo
,.. . . . . .. . ...-. . . . .
corngo, lnClUlda una mta dlkerente ue cmes g~oba~es.
. . - . . . .
c s esta usta ae c ~ a -
a .

ses globales y no el uso de la misma interfaz lo que deberia dupIicarse en


memoria.

Estructura de un paquete
Podriamos preguntarnos si es posible saber si una unidad ha sido enlazada en
el fichero ejecutable o si forma parte de un paquete en tiempo de ejecucion. No
solo esto es posible en Delphi, sino que incluso se puede explorar la estructura
general de una aplicacion. Un componente puede utilizar la variable global
Module I s Pac kage, declarada en la unidad SysInit. No deberiamos necesitar
esta variable, per0 es tecnicamente posible tener un componente con codigos dife-
rentes dependiendo de si esta empaquetado o no. El siguiente codigo extrae el
paquete en tiempo de ejecucion que alberga a1 componente, si lo hubiera:
var
fPackName: string;
begin
// o b t i e n e e l n o m b r e d e l p a q u e t e
SetLength (fPackName, 100);
i f ModuleIsPackage then
begin
GetModuleFileName (HInstance, PChar (fPackName), Length
(fP a c k N a m e ) ) ;
fPackName : = PChar (fPackName) // a j u s t a l a l o n g i t u d d e
l a cadena
end
else
fPackName : = ' N o t p a c k a g e d ' ;
Ademas de acceder a la informacion sobre el paquete desde el interior del
componente (corno en el codigo anterior), tambien podemos hacer lo mismo desde
un punto de entrada especial de las bibliotecas de paquetes, la funcion
G e t P a c k a g e I n f o T a b l e . Esta devuelve cierta informacion especifica sobre
el paquete que Delphi guarda como recursos e incluye en el paquete DLL. Afortu-
nadamente, no necesitamos tecnicas de bajo nivel para acceder a esta informa-
cion, porque Delphi ofrece algunas funciones de alto nivel para manipularlo.
Podemos usar dos funciones para acceder a la informacion sobre el paquete:
GetPackageDescription: Devuelve una cadena que contiene una descrip-
cion del paquete. Para llamarla, tenemos que dar el nombre del modulo (la
biblioteca de paquetes) como unico parametro.
GetPackageInfo: No devuelve directamente informacion sobre el paquete,
sino que hay que pasarle una funcion a la que llamar para cada entrada de
la estructura de datos interna del paquete. En la practica, G e t P a c k a -
g e 1n f o llamara a nuestra funcion para cada unidad contenida en el pa-
quete. Ademas, G e t P a c k a g e I n f o define varios indicadores en una
variable I n t e g e r .
Estas dos llamadas a funciones nos permiten acceder a informacion interna
sobre un paquete, per0 para saber que paquetes esta usando nuestra aplicacion,
podemos mirar el archivo ejecutable que utilizan las funciones de bajo nivel. Sin
embargo, Delphi nos proporciona un metodo mas sencillo. La funcion
EnumModules no devuelve directamente informacion sobre 10s modulos de la
aplicacion, sino que nos permite pasarle una funcion, a la que llama para cada
modulo de la aplicacion, el archivo ejecutable principal y para cada uno de 10s
paquetes que necesita la aplicacion.
Para demostrar esta tecnica, hemos construido un sencillo ejemplo que mues-
tra la informacion sobre paquetes y modulos en un componente TreeView. Cada
nodo del primer nivel corresponde a un modulo y dentro de cada modulo hemos
creado un subarbol que muestra 10s paquetes que contiene y necesarios para dicho
modulo, asi como la descripcion del paquete y 10s indicadores de compilacion
(RunOnly y D e s i g n O n l y ) . Podemos ver el resultado de este ejemplo en la
figura 10.6.
Ademas del componente TreeView, hemos aiiadido otros componentes a1 for-
mulario principal, per0 10s hemos ocultado: un DBEdit, un Chart y un
FilterComboBox. Hemos aiiadido estos componentes para incluir mas paquetes
en tiempo de ejecucion en la aplicacion, ademas de 10s ubicuos paquetes VCL y
RTL. El unico metodo de la clase de formularios es F o r m C r e a t e , que llama a
la funcion de enumeracion del modulo:
procedure TForml. FormCreate (Sender: TObject) ;
begin
EnumModules (ForEachModule, nil) ;
end;
D Contams
Packlnfo [ M a ~ nUnd )
PackFo~m
Sysln~t
Requ~res
n C.\WINDOWS\System3Z\vcldb70 bpl
.: Contalns

Syslnrl
VDQConsta
DBOleCll
DBCtrls
DBLogDlg
DBPclns
dbcgr~ds
DBGrlds
+ Flequurs
Deicr~p(~on Borland Database Components
Fun Drily
- C \~dINDOWS\Syslem32\dbrII70 bpl
+ Conta~ns
+ Rcqu~res
Descnpt~or~ Borland Core Dslabase Components
Run Only
- r \ \ A I I N ~ ~ \ A I < \ < , , * I P ~M~~\I~-~~

Figura 10.6. El resultado del ejernplo Packlnfo, con la lista de paquetes que usa.

La funcion EnumModules acepta dos paramctros. El primcro cs la funcion


de retrollamada (o callback). quc en nuestro caso scra F o r E a c h M o d u l e , y el
scgundo es un puntero a la cstructura de datos que utilizara la funcion de respues-
ta a la llamada (en nuestro caso. n i l , puesto que no necesitamos esto). La fun-
cion de rcspuesta a la llamada habra de accptar dos parametros (un valor
H I n s t a n c e y un puntero sin tipo) y habra dc dcvolvcr un valor booleano. La
funcion EnumModules llamara, a su vcz. a nuestra funcion de respuesta a la
llamada para cada modulo. pasando el manejador de la instancia de cada modulo
como primcr parametro y cl puntero de estructura de datos ( n i l cn el ejemplo)
como el segundo:
f u n c t i o n ForEachModule (HInstance: Longint;
Data: Pointer) : Boolean;
var
Flags: Integer;
ModuleName, ModuleDesc: string;
ModuleNode: TTreeNode;
begin
w i t h Forml .Treeview1 . Items d o
begin
SetLength (ModuleName, 200) ;
GetModuleFileName (HInstance,
PChar (ModuleName), Length (ModuleName)) :
ModuleName : = PChar (ModuleName) ; / / a j u s t a
ModuleNode : = Add (nil, ModuleName) ;

/ / o b t i e n e l a description y a d a d e n o d o s a j u s t a d o s
ModuleDesc : = GetPackageDescription (PChar (ModuleName));
ContNode : = AddChild (ModuleNode, ' C o n t a i n s ' ) ;
ReqNode : = AddChild (ModuleNode, ' R e q u i r e s ' ) ;

// afiade information s i e l modulo e s un p a q u e t e


GetPackageInfo (HInstance, nil, Flags, ShowInfoProc);
if ModuleDesc <> " then
begin
AddChild (ModuleNode, ' D e s c r i p t i o n : ' + ModuleDesc);
if Flags and pfDesignOnly = pfDesignOnly then
AddChild (ModuleNode, ' D e s i g n O n l y ' ) ;
if Flags and pfRunOnly = pfRunOnly then
AddChild (ModuleNode, ' R u n O n l y ' ) ;
end ;
end ;
Result : = True;
end:

Como podemos ver en el codigo anterior, la funcion F o r E a c h M o d u l e co-


mienza aiiadiendo el nombre del modulo como nodo principal del arb01 (llamando
a1 metodo Add del objeto Treeview1 . I t e m s y pasando n i l como primer
parametro). A continuacion, aiiade dos nodos hijos fijos, que se guardan en las
variables C o n t N o d e y ReqNode declaradas en la parte de implementation de
esta unidad.
Como paso siguiente, el programa llama a la funcion G e t P a c k a g e I n f o y
le pasa otra funcion de retrollamada, Show 1n f o P r o c , para obtener una lista de
las unidades de la aplicacion o del paquete. A1 final d e l a funcion
F o r Ea c hModule, si el modulo es un paquete el programa aiiade mas informa-
cion, como su descripcion y sus indicadores de compilation (el programa sabe
que es un paquete si su descripcion no es una cadena vacia).
Anteriormente, hemos mencionado el paso de una funcion de retrollamada (el
procedimiento S h o w I n f o P r o c ) a la funcion G e t P a c k a g e I n f o , que, a su
vez, llama a la funcion de retrollamada para cada paquete contenido o requerido
de un modulo. Este procedimiento crea una cadena de caracteres que describe el
paquete y sus indicadores principales (agregados entre parentesis), y despues
inserta esa cadena bajo uno de 10s dos nodos (ContNode y ReqNode), depen-
diendo del tipo de modulo. Podemos determinar el tipo de modulo examinando el
parametro NameType. Este es el codigo de la segunda funcion de retrollamada
a1 completo:
procedure ShowInfoProc (const Name: string; NameType:
TNameType;
Flags: Byte; Param: Pointer);
var
FlagStr: string;
begin
FlagStr : = ' ' ;
if Flags and ufMainUnit <> 0 then
FlagStr : = FlagStr + ' M a i n U n i t ' ;
i f Flags and ufPackageUnit <> 0 then
FlagStr : = FlagStr + ' P a c k a g e U n i t ' ;
i f Flags and ufWeakUnit <> 0 then
FlagStr : = FlagStr + ' W e a k U n i t ' ;
i f FlagStr <> ' ' then
FlagStr : = ' ( ' + FlagStr + I ) ' ;
with Forml.TreeViewl.Items do
c a s e NameType o f
ntContainsUnit : AddChild (ContNode, Name + FlagStr) ;
ntRequiresPackage: AddChild (ReqNode, Name);
end;
end ;
Modelado
y programacion
orientada
a objetos (con
ModelMaker)
Cuando Borland decidio ofrecer una solucion de diseiio UML para las edicio-
nes Enterprise y Architect de Delphi 7, escogio incluir ModelMaker, de
ModelMaker Tools ofHolland (www.modelmakertools.com).ModelMaker es una
herramienta de diseiio UML de alta calidad integrada en el IDE de Delphi. Pero a
medida que entremos en contact0 con ModelMaker, veremos que es mucho mas
que una herramienta para diagramas UML. Hablaremos, por supuesto, de las
facilidades que ofrece ModelMaker para realizar diagramas UML, per0 tambien
de otras caracteristicas de la herramienta asi como de una vision global concep-
tual del product0 que deberia permitir su maximo aprovechamiento.
ModelMaker ha existido desde 10s primeros dias de Delphi, y con el tiempo ha
acumulado opciones para soportar casi por completo el lenguaje Delphi a1 igual
que una gran cantidad de utiles recursos para programadores. El resultado es un
impresionante conjunto de caracteristicas atractivo a primera vista. La interfaz de
usuario de ModelMaker incluye mas de 100 formularios y, sin unos conocimien-
tos adecuados, puede ser muy frustrante para el recien iniciado.
Aunque ModelMaker suele llamarse herramienta de diagramacion UML, es
preferible describirla como una herramienta CASE y de diagramas UML de ciclo
completo, extensible, personalizable y especifica para Delphi. Es especifica para
Delphi porque se diseiio para manejar codigo Delphi. Por ejemplo, cuando se
trabaja con una propiedad, 10s cuadros de dialog0 de ModelMaker presentan
opciones que son especificas a palabras y conceptos clave del lenguaje Delphi.
ModelMaker es personalizable porque cientos de opciones controlan el mod0 en
que se genera el codigo Delphi a partir del modelo de objetos. ModelMaker es
extensible porque incluye una API OpenTools muy robusta que permite la crea-
cion de ampliaciones expertas para extender la funcionalidad del producto. Se
trata de una herramienta de ciclo completo porque ofrece caracteristicas que se
aplican a todas las fases de un proceso de desarrollo estandar. Por ultimo,
ModelMaker puede describirse como una herramienta CASE porque generara
automaticamente parte del codigo obvio y redundante necesario para las clases de
Delphi, dejando a1 programador con la tarea de proporcionar el codigo operativo
de las clases.
Este capitulo ha sido coescrito con Robert Leahey y se basa en gran medida en
su conocimiento e intensa experiencia con ModelMaker. En el mundo del soft-
ware, Robert es un arquitecto, programador, autor y conferenciante. Como musi-
co, ha tocado profesionalmente durante mas de 20 aiios y actualmente es un
estudiante graduado en la University of North Texas en el area de la teoria musi-
cal. A traves de su empresa, Thoughtsmithy (www.thoughtsmithy.com), Robert
ofrece servicios de consultoria e instruccion, software comercial, produccion de
sonido y esculturas LEG0 de gran tamaiio. Vive en el norte de Texas con su
mujer e hijas. Este capitulo trata 10s siguientes temas:
Conceptos de ModelMaker
Modelado y UML.
Caracteristicas de codification de ModelMaker.
Documentacion y macros.
Reingenieria de codigo.
Implementacion de patrones.

Comprension del modelo interno


de ModelMaker
Antes de empezar a comentar el soporte UML de ModelMaker y otras caracte-
risticas, es vital comprender conceptualmente como gestiona ModelMaker el mo-
d e l de
~ codigo. A1 contrario que Delphi y otros editores, ModelMaker no reprocesa
continuamente un archivo de codigo fuente para representar visualmente su con-
tenido. Fijemonos en Delphi: Cualquier facilidad del IDE que se usa para modifi-
car el codigo cambiara directamente el contenido del archivo del codigo fuente
(que puede guardarse para que 10s cambios permanezcan). Por contra, ModelMaker
mantiene un modelo interno que representa las clases, el codigo, la documenta-
cion y demas, a partir del cual se generan 10s archivos de codigo fuente. Cuando
se edita el modelo mediante 10s diversos editores de ModelMaker, 10s cambios se
aplican a1 modelo interno (no a 10s archivos externos de codigo, a1 menos no hasta
que se indica a ModelMaker que vuelva a generar 10s archivos externos). Com-
prender esta diferencia nos ahorrara una importante cantidad de frustracion.
Otro concept0 importante es que ModelMaker es capaz de representar un uni-
co modelo de codigo interno a traves de multiples vistas en su interfaz de usuario.
Por ejemplo, el modelo puede verse y editarse como una jerarquia de clases, o
como una lista de unidades con clases contenidas en ellas. Los miembros de clase
pueden ordenarse, filtrarse, agruparse y editarse de muy diversas maneras. Cual-
quier numero de vistas puede verse en las diversas extensiones disponibles para
ModelMaker. Pero, lo que es mas importante, el propio editor de diagramas UML
es otra vista mas del modelo. Cuando se visualizan 10s elementos del modelo
(corno clases y unidades) en 10s diagramas, se crean representaciones visuales de
10s elementos del modelo de codigo; si se elimina un simbolo de un diagrama, no
se esta borrando necesariamente el elemento del modelo, simplemente se elimina
su representacion en el diagrama. Aunque ModelMaker ofrece diversos asistentes
y prestaciones de automatizacion en la visualizacion, el product0 no leer6 el codi-
go y generara automaticamente unos diagramas UML atractivos sin ningun es-
fuerzo por parte del programador. Despues de importar el codigo fuente y aiiadir
las clases a 10s diagramas, se necesitara recolocar 10s simbolos para crear diagramas
UML utiles.

Modelado y UML
UML (Unified Modeling Language) es una noticacion grafica usada para ex-
presar el analisis y diseiio de un proyecto software y comunicarselo a otras perso-
nas. UML es independiente del lenguaje, per0 esta pensado para la descripcion de
proyectos orientados a objetos. Como resaltan 10s creadores de UML, no se trata
de una metodologia, y puede usarse como una herramienta descriptiva sin impor-
tar cual sea el proceso de diseiio preferido.
Vamos a fijarnos en 10s diagramas UML desde el punto de vista de un progra-
mador de Delphi que use ModelMaker.

Diagramas de clase
Uno de 10s diagramas UML mas comunes soportados por ModelMaker es el
diagrama de clase. Los diagramas de clase pueden mostrar una amplia variedad
de relaciones de clase, pero, en su minima expresion, este tipo de diagrama mues-
tra un conjunto de clases u objetos y las relaciones estaticas existentes entre ellos.
Por ejemplo, la figura 1 1.1 muestra un diagrama de clase que contiene las clases
del programa NewDate de un capitulo anterior. Si 10s resultados son distintos
cuando se importen estas clases en ModelMaker y se Cree un diagrama de clase
propio; hay que tener en cuenta las numerosas opciones comentadas anteriormen-
te. Muchos parametros controlan el mod0 en que se muestran las clases
visualizadas. Se puede abrir el archivo de ModelMaker (MPB) usado para la
figura 1 1.1 desde la carpeta de codigo fuente del capitulo actual.

- IDBle. TDdeTne;
+ Day: Inleger;
.
atlrlbr~CS
Mo* Inleger.
Yaar M*p:
0Wnions
- GetDay IMeger; Assign(. )
- G e t ~ o m hhleger; Denease(..)
Getyear, Imeger, + GelTeut. d r h g
- SefDay( .) +Increase(..)
- SelManlh(..) + Leapyea. BncFa~nc~a.
- SelYear( ) SelVW.I
+ Gesde r Setvalue( )
+Create( )

Anteriormente, comentamos que el editor de diagramas de ModelMaker es


simplemente otra vista mas del modelo interno. Algunos de 10s simbolos presentes
en 10s diagramas de ModelMaker se proyectan directamente sobre elementos del
modelo de codigo y otros no. Con 10s diagramas de bajo nivel como 10s diagramas
de clase, la mayoria de 10s simbolos representan elementos reales del modelo de
codigo. Manipular estos simbolos puede modificar el codigo generado por
ModelMaker. Como caso contrario, en 10s diagramas de casos de uso la mayor
parte (si no todos) de 10s simbolos no tienen representacion dentro del modelo de
codigo. En 10s diagramas de clase se pueden aiiadir nuevas clases, interfaces,
campos, propiedades e incluso documentacion a1 modelo. Del mismo modo, se
puede modificar la herencia de una clase en el modelo desde el propio diagrama.
A1 mismo tiempo, se pueden afiadir diversos simbolos a un diagrama de clase que
no tengan una representacion Iogica dentro del modelo de codigo.
Los diagramas de clase en ModelMaker tambien permiten codificar interfaces,
trabajando a un mayor nivel de abstraccion. La figura 11.2 muestra las relaciones
entre las clases y las interfaces en un ejemplo complejo de uso de interfaz, IntfDemo.
PUS htegsr,

check in rmwt Ncur~lssham

Figura 11.2. Un diagrarna de clase con interfaces, clases y delegacion de interfaces.

Cuando se usan'interfaces en 10s diagramas de clase, se puede especificar las


relaciones de implementacion de interfaces entre clases e interfaces, y esas
implementaciones se afiadiran a1 modelo de codigo. Aiiadir la implementacion de
una interfaz en un diagrama implica la aparicion de una de las prestaciones de
ModelMaker mas atractivas: el asistente Interface Wizard (vease figura 11.3).
Activar el Interface Wizard para una clase simplifica en gran medida la tarea
de implementar un interfaz. El asistente enumera 10s metodos y propiedades que
necesita una clase para implementar una interfaz (o interfaces) dadas; si se orde-
na, el asistente aiiadira todos estos miembros necesarios a la clase de
implementacion. Fijese que depende del programador proporcionar un codigo con
significado para cualquier metodo aiiadido a la clase. En la figura 11.3, el asis-
tente evalua TAhlete para su implementacion de IWalker e IJumper y sugiere
10s cambios que son necesarios para la correcta implementacion de estas interfaces.

Diagramas de secuencia
Los diagramas de secuencia modelan la interaccion entre objetos representan-
do 10s objetos y 10s mensajes transmitidos entre ellos a lo largo del tiempo. En un
diagrama de secuencia tipico, 10s objetos que interactuan dentro de un sistema se
organizan a lo largo del eje X y el tiempo se representa desde arriba hacia abajo,
a lo largo del eje Y. Los mensajes se representan como flechas entre objetos. Se
puede ver un ejemplo de un diagrama de secuencia bastante trivial en la figura
1 1.4. Los diagramas de secuencia pueden crearse a diversos niveles de abstrac-
cion, con lo que se permite representar la interaccion del sistema a alto nivel,
con solo unos pocos mensajes, o una interaccion a bajo nivel, con muchos men-

3
- t h i i lrxepn
IJumpei
-- TAlh!.et~

Tkhlele
= SetPosp'alue: Inlegerj;
--
TAlhkle
= GelPor Inleper; TAlhkle
J w *;le

3
- w a :rcr*
Podlan-InNpe,
Redundanl rnembar TAthlele
+ C~ede. TAIhlete Leave m!undanl rnrrbs
+ Deshy. TAlhlele Leave ~ A n d a nml d m f
+ IJumplrnpl TJurnperlrnpl. TAthlele Leave ledurdanl memba
+ Jurpcl TJurnperlrnpl TAtHele Leave rcdmdanl member
+ Wdkl: dnng: TAlhlcte Leave l e d d a n t mcmbcr

Figura 11.3. El asistente Interface Wizard de ModelMaker.

Junto con 10s diagramas de clase, 10s diagramas de secuencia son unos de 10s
diagramas UML soportados por ModelMaker que se relacionan mas de cerca con
el modelo de codigo.
Se pueden crear varios diagramas en ModelMaker en 10s cuales 10s simbolos
usados no tienen una relacion directa con el modelo de codigo, per0 en 10s diagramas
de secuencia se puede afectar directamente a1 codigo de las clases del modelo y a
sus metodos. Por ejemplo, cuando se crea un simbolo para un mensaje entre
objetos. se puede escoger de entre una lista de metodos que corresponden a1 objeto
receptor, o se puede decidir afiadir un metodo nuevo a1 objeto, y el nuevo metodo
se aiiadira a1 modelo de codigo. Fijese en que tal y como se comento, ModelMaker
no creara automaticamente diagramas de secuencia para el codigo importado;
sera necesario crearlos de forma manual.

Casos de uso y otros diagramas


Acabamos de comentar dos de 10s diagramas UML de menor nivel en primer
lugar, per0 ModelMaker soporta varios diagramas UML de mayor nivel disefia-
dos para ofrecer un camino desde el modelado de la interaccion del usuario a1
mayor nivel de 10s diagramas de casos de uso hasta 10s diagramas de clases y
secuencia de bajo nivel. Los diagramas de caso de uso estan entre 10s diagramas
mas utilizados, a pesar de que sus simbolos no tengan ninguna relacion con 10s
elementos del modelo de codigo. Estos diagramas estan pensados para modelar lo
que se supone que hace el software, y son lo suficientemente autoesplicativos
como para usarlos en sesiones de analisis con personas que no sean desarrolladores.

-13 s 7 u +i k ru

+ Oeatcly rn b lntepn)
a PissglSwce 7 0 4 4
* 3 Dec~eeselNumbetOlDayr
, 3 Ge(Tex( simg
+
+, Increas$4mbnOiD~
* Leapyea Bookan

1
, iu a !a( ChFck h Insert Nmw~-nct-~.m
Figura 11.4. Un d~agramade secuencla para un controlador de eventos del ejemplo
NewDate.

Un diagrama de caso de uso simple consiste en actores (usuarios o subsistemas


de la aplicacion) y casos de uso (cosas que hacen 10s actores). Una de las pregun-
tas mas frecuentes relativas a 10s casos de uso es como manejar 10s textos de 10s
casos de uso en ModelMaker.
Los textos para 10s casos de uso son un paso siguiente tipico cuando se realiza
un analisis preliminar. Por ejemplo, un caso de uso es una breve descripcion de
una accion que puede emprender un actor ("Obtener una vista previa de un infor-
me de ventas" o "Ajustar el tamaiio de una ventana"); el texto de un caso de uso es
una descripcion mas detallada del texto. ModelMaker no soporta especificamente
10s testos de casos de uso mas grandes; se puede usar un simbolo de anotacion
dentro del diagrama conectado a1 simbolo del caso de uso, o se puede vincular el
simbolo del caso de uso a un archivo externo que contenga el texto del caso
de uso. El resto de diagramas UML soportados por ModelMaker son 10s siguien-
tes:
Diagramas de colaboraci6n: Diagramas de interaccion, muy parecidos a
diagramas de secuencia. Sin embargo, se diferencian en que el orden de 10s
mensajes se especifica mediante numeracion en lugar de emplear una esca-
la de tiempo. Esto produce una disposicion de diagrama distinta en la que
las relaciones entre 10s objetos pueden verse en ocasiones de manera mas
clara.
Diagramas de estado: Diagramas que describen el comportamiento de un
sistema identificando todos 10s estados que puede asumir un objeto como
resultado de 10s mensajes que recibe. Un diagrama de estado deberia mos-
trar una lista de todas las transiciones de estado a que se encuentra sujeto
un objeto, indicando 10s estados inicial y final de cada transicion.
Diagramas de actividad: Diagramas que muestran el flujo de un sistema y
son particularmente utiles para visualizar procesamiento en paralelo.
Diagramas de componentes y despliegue: Tambien conocidos como
diagramas de implementacion. Diagramas que permiten modelar las rela-
ciones entre 10s componentes (modulos, en realidad ejecutables, objetos
COM, DLL, etc...) o, en el caso de 10s diagramas de despliegue, 10s recur-
sos fisicos (que suelen llamarse nodos).

Diagramas no UML
ModelMaker soporta tres diagramas que no son estandar de UML, per0 son
bastante utiles:
Diagramas de mapa mental (Mind-Map): Creados por Tony Buzan en la
decada de 1960. Un metodo excelente para tormentas de ideas, explorar
temas con ramificaciones o registrar rapidamente pensamientos encadena-
dos. Suelen usarse para mostrar datos genericos durante presentaciones.
Diagramas de dependencia de unidades: Suelen usarse para mostrar 10s
resultados del potente Unit Dependency Analyzer de ModelMaker. Estos
diagramas pueden mostrar relaciones ramificadas de unidades dentro de un
proyecto Delphi.
Diagramas de robustez: Estos diagramas se han dejado fuera de la espe-
cificacion UML, tal vez sin mucho sentido. Ayudan a salvar el obstaculo
entre el modelado de casos de uso de solo interfaces y la implementacion de
diagramas de secuencia especificos. Los diagramas de robustez pueden
ayudar a un equipo de analisis a verificar sus casos de uso y orientarse
hacia 10s detalles de la implementacion.
Elementos comunes de 10s diagramas
Cada tip0 de diagrama soportado por ModelMaker contiene simbolos especifi-
cos para ese tipo de diagrama, per0 existen varios elementos en el Diagram Editor
que son comunes a todos 10s tipos de diagrama. Se pueden aiiadir imagenes y
formas a 10s diagramas, a1 igual que simbolos de paquetes (contenedores a 10s que
se pueden aiiadir otros simbolos).
El simbolo de hiperenlace permite aiiadir a un diagrama una etiqueta enlazada
con alguna otra entidad. De hecho, la amplia mayoria de 10s simbolos de diagra-
ma soportan esta caracteristica de hiperenlace. Se puede enlazar a otro diagrama
(hacer clic sobre el enlace abrira el diagrama enlazado en el editor), se puede
enlazar a un elemento dentro del modelo de codigo (una clase, unidad, metodo,
interfaz,. . ., de manera que a1 hacer clic sobre el enlace se abra el editor apropiado
para el elemento enlazado) o se puede enlazar a un documento externo (este enla-
ce abrira el documento enlazado con la aplicacion apropiada).
Hay disponibles tres tipos distintos de herramientas de anotacion para cada
tip0 de diagrama. Varios documentos explican con mas detalle estas herramien-
tas, baste pues decir aqui que se puede aiiadir un simbolo de anotacion indepen-
diente, uno que muestre automaticamente la documentacion interna del objeto
enlazado o aiiadir un simbolo de anotacion, escribir texto en el y enlazar este
simbolo a un objeto. Cuando se hace esto, la documentacion interna del objeto se
actualiza para que se corresponda con el texto del simbolo de anotacion.
Los simbolos de relacion o asociacion son de manera predeterminada lineas
rectas. Sin embargo, se puede convertir en lineas ortogonales seleccionando el
simbolo y pulsando Control-0. Tambien se puede aiiadir notas a estos simbolos
pulsando Control y haciendo clic sobre la linea. ModelMaker intenta mantener
estos simbolos ortogonales siempre que sea posible.
Ahora, ModelMaker ofrece un robusto conjunto de estilos de simbolos visua-
les. Se pueden definir estilos de fuente y color de un mod0 jerarquico y aplicarlos
por nombre a 10s simbolos del diagrama. Para conseguir mas informacion, es
aconsejable buscar "style manager" en la ayuda electronica.
Una ultima caracteristica habitual a resaltar es la capacidad de ordenar
jerarquicamente la lista de diagramas (vease figura 11.5). Se pueden aiiadir car-
petas de cara a la organizacion y reordenar 10s diagramas. Para ello basta con
mantener pulsada la tecla Control y arrastrar un diagrama a su nuevo diagrama
padre.
Las prestaciones de diagramas de ModelMaker ofrecen una amplia gama de
posibilidades; una vez que se haya dedicado algo de tiempo a comprender el
conjunto de prestaciones, seguramente se descubra que el proceso de desarrollo se
transforma. Para el desarrollador de Delphi, la naturaleza activa bidireccional del
Diagram Editor ofrece una experiencia de diagramacion mucho mas dinamica que
la mayoria de 10s editores UML que simplemente generan imagenes estaticas bien
acabadas.
M!;$
,T SUML

almplemenlabonD~agram
Collaboral~onDlagram
m ~ l a s D~aglam
s
Use Case D~agram
.Yr I.^ ,

Figura 11.5. Las posibilidades organizativas de la vista Diagrams

Caracteristicas de codificacion de ModelMaker


Como se ha visto, ModelMaker es una potente herramienta de diagramas UML
y se puede realizar una gran cantidad de trabajo de analisis y disefio aprovechan-
do solo estas caracteristicas. Pero ModelMaker ofrece mucho mas que simple-
mente diagramas. Muchos desarrolladores usan ModelMaker como su entorno de
dcsarrollo principal, sustituyendo a Delphi a ese respecto. Esto se debe en parte a1
mod0 visual en que ModelMaker representa las tareas de programacion en Delphi,
automatizando muchas de las partes repetitivas de la codificacion de clases Delphi.
Pero tambien es asi porque Delphi, a pesar de sus puntos fuertes, tiende a facilitar
el desarrollo de codigo en 10s puntos en que la linea entre 10s dominios de presen-
tacion y de problema se wielve borrosa.
En otras palabras, es facil escribir codigo de implementation para una aplica-
cion (el codigo que realmente hace algo) y ponerlo directamente en 10s controladores
de eventos de 10s formularios. Tipicamente esto no se considera un buen disefio
orientado a objetos. Por otra parte, ModelMaker facilita la creacion y reingenieria
de 10s objetos del dominio del problema y la separation entre esos objetos y la
interfaz de usuario. Antes de continuar, comentaremos como trabajan juntos Delphi
y ModelMaker.

Integracion Delphi I ModelMaker


Cuando se instala correctamente, ModelMaker afiade un menu a1 IDE de Delphi,
etiquetado como ModelMaker. Si no se ve este menu, se necesitara instalar el
asistente basado en DLL de ModelMaker en el registro de Delphi, tal y como se
comenta a1 final del primer capitulo. Desde este menu se puede controlar ligera-
mente ModelMaker e importar rapidamente codigo en proyectos de ModelMaker.
jd R_UnModelMaker
16 4 &mp to ModelMaker Sh~ftcCtrlcM
Q, Add to Model
@&Add flies to Mode(. .
Rsfresh In Model Sh~ft+Ctrl+H
I Cmvert to Model
Cgnvert pro@ to Model
j OpmModel
: Inlegratan Opkm.. .
Version Control b
I R-ce S t r r q W~zard b

8s -cut Wizard. .
La mayoria de las opciones de menu solo se encuentran disponibles si
ModelMaker esta en funcionamiento. Una vez que se haya arrancado ModelMaker
(desde la opcion de menu Run ModelMaker o de un mod0 normal), el resto de
10s elementos quedara disponible. El menu de integracion contiene unos cuantos
modos de aiiadir codigo a un modelo. Los elementos Add to Model,Add Files
to Model, Convert to Model y Convert Project to Model hacen
que ModelMaker importe las unidades especificadas: 10s elementos Add impor-
tan unidades en el modelo cargado actualmente en ModelMaker, y 10s elementos
Convert crean un modelo nuevo e importan las unidades.
Convert Project to Model es un buen sitio para comenzar (nos asegurare-
mos de hacer una copia del codigo, y despues seleccionaremos este elemento del
menu mientras que esta abierto uno de 10s proyectos en Delphi). El proyecto a1
completo se importara a un nuevo modelo en ModelMaker.

aD6nde esta la VCL? Herencia e importaci6n de c6dlgo


en ModelMaker
Despues de examinar el codigo recien importado en ModelMaker, puede
verse que solo se importaron las unidades que forman parte &l proyecto.
ModelMaker no importara automaticamente unidades usadas por el pro-
yecto (si se hiciera esto se crearian rnodelos inmaswiamente gmdes con
muchas clases importadas innecesariamente). Sin embargo, muchas de las
mejores caracteristicas relacionadas con la herencia de ModeMaker nece-
sitan que las clases antecesoras existan en el modelo de cbdigo. (F%r ejem-
ploycuando se preparan correctamente, 10s cambios en las clases antecesoras
se propagarin automhticamente sobre 10s descendienfes.)
Afortunadamente, disponemos de muchas opciones durante la importacih
de c6digo. Aunque se pueda importar m e w e eI menu de integrkibn de
ModelMaker en Delphi (0 arrastrando ma unid-4desdc el Elwpl~dm&
Windows sobre ModelMaker), el mod0 m&9 flexible es usa~una &+.Icra
botones de importaci6n de la barra de lmmmh&s grnmpd & ModelMakci.
Importar unidades de esta manera hara que aparezca el cuadto de d i i h g o
Import Source File, donde se pueden fijar las opciones sobre el mod0 de
importar el cbdigo. Aprovechar estas opciones permite importar parte de la
VCL como clases de reserva para que se puedan emplear las herrarnientas
de herencia de ModelMaker sin inflar desmedidamente el modelo.

Tambien en el menu de integracion, esta Refresh in Model, que obliga a


ModelMaker a volver a importar la unidad actual. Ahora es un buen momento
para comentar una de las consecuencias del modelo de codigo interno de
ModelMaker comentado anteriormente. Ya que ModelMaker trabaja sobre su
codigo interno y no sobre 10s archivos de codigo f~ienteesternos (hasta que se
vuelven a generar 10s archivos), es habitual descubrir que se han editado tanto el
modelo como 10s archivos de codigo, con lo que el modelo habra perdido la sin-
cronia con respecto a 10s archivos fuente. Cuando han cambiado 10s archivos
fuente per0 no el modelo, se puede vol\~era sincronizar el modelo volviendo a
importar las unidades fuente. Pero si tanto el modelo como 10s archivos han
cambiado, la situacion es mas complicada. Afortunadamente, ModelMaker ofre-
ce un robusto conjunto de herramientas para manejar problcmas de sincronizacion.
En la seccion "Vista de diferencias" se puede ver mas informacion a1 respccto.
Otro elemento notable en el mcnu de integracion es Jump to ModelMakcr.
Cuando se sclecciona este elemento, ModelMaker trata de encontrar la posicion
actual del codigo dentro de su modelo cargado, trayendo ModelMaker a1 frente
del proceso.
Aunque ModelMaker puede controlarsc desde Delphi, la integracion es
bidirectional. Al igual que el menu de ModelMaker en el IDE de Delphi, un mcnu
Delphi aparece en ModelMaker. En esc menil se encuentran comandos que per-
miten saltar del modelo seleccionado actualmente a su posicion correspondiente
en el archivo de codigo fuente en Delphi, al igual que comandos que provocan que
Delphi realice una verificacion de sintaxis, compile o construya el proyecto. Por
eso se puede editar el codigo, generarlo y compilarlo, todo ello desde ModelMaker.

Gestion del modelo de codigo


Es el momento de esplicar las interioridades de la codificacion con ModelMaker.
Debido a la naturaleza orientada a objetos del modelo de codigo interno de
ModelMaker, editar elementos del modelo de codigo es un proceso tipicamente
mas visual que en Delphi. Por ejemplo, editar una propiedad de clase se hace
mediante el cuadro de dialog0 Property Editor, como muestra la figura 11.6.
Este es uno de 10s mejores ejemplos de la autornatizacion de ModelMaker. Cuan-
do se aiiade una nueva propiedad, no hay que preocuparse por aiiadir ademas un
campo de estado privado, ningun metodo de lectura o escritura o siquiera la
declaracion de la propiedad.
r User named -kccrr opsaias r, ,,

Figura 11.6. El cuadro de dialogo Property Editor.

Todo lo que se hace es escoger 10s parametros apropiados en el editor. y


ModelMaker creara 10s miembros de clase de soporte necesarios. Esto es algo que
vas mas alla de las ventajas que ofrece el IDE de Delphi con su Class Completion.
Fijese en que 10s atributos de una propiedad que normalmente habria que es-
cribir manualmente estan representados por varios controles en el cuadro de dia-
logo. Las especificaciones V i s i b i l i t y , Type, Read, Write y otras se
gestionan desde el editor. Las ventajas tienen que ver con el ambito de la reingenieria
(por no mencionar la elimination de ciertas tareas de escritura repetitivas). Por
e.jemplo, ya que ModelMaker gestiona una propiedad como un objeto en su mode-
lo de codigo, modificar algo sobre la propiedad (como su tipo) provocara que
ModelMaker aplique ese cambio a cualquier referencia de la que tenga conoci-
miento. Si despues se desea modificar el acceso de lectura desde un campo a un
metodo, se puede hacer ese carnbio en el editor, y ModelMaker se encargara de
aiiadir el metodo get y modificar la declaracion de la propiedad. Lo que es mejor,
si se decide renombrar la propiedad o llevarla a otra clase, la propiedad dispone
de sus propios miembros de clase de soporte: automaticamente cogeran otro nom-
bre o se desplazaran tal y como resulte apropiado.
El mismo enfoque se usa para cada uno de 10s tipos de miembros de clase;
existen editores similares para metodos, eventos, campos e incluso clausulas de
resolution de metodos.
Existe un cierto sentido de abstraccion en el nivel del desarrollador para desa-
rrollar en ModelMaker. Libera a1 desarrollador de la necesidad de pensar en
detalles de implementacion durante la edicion de 10s miembros de clase; simple-
mente se necesita pensar en terminos de interfaz, mientras que ModelMaker se
encarga de la mayor parte de las tareas repetitivas de la implementacion del miem-
bro. (No hay que confundir esta metafora con la escritura del codigo de la
implementacion de un metodo, que sigue resultando necesaria.)

El editor Unit Code Editor


ModelMaker incluye dos editores de codigo: el editor para la implementacion
de metodos de clase y el Unit Code Editor, que requiere algo de explicacion.
ModelMaker es realmente una herramienta orientada a objetos/clases (sus presta-
ciones se basan principalmente en la gestion de codigo a1 nivel de clase). Cuando
se trata del codigo que no es parte de una clase (declaraciones de tipos que no son
clases, declaraciones de metaclases, metodos y variables de unidad, etc. ..), Model
Maker adopta un enfoque mas sencillo. Cuando ModelMaker importa una uni-
dad, todo lo que puede guardarse dentro del modelo de codigo se maneja de acuer-
do con ello, y lo que queda fuera aparece en el Unit Code Editor. (Normalmente,
para 10s nuevos usuarios, esto incluye cualquier documentacion que no entre en
las implementaciones de metodos, per0 ModelMaker puede hacer reingenieria
tambien con la documentacion.)
Lo siguiente es un ejemplo de lo que podria verse en el Unit Code Editor:
unit <!UnitName!>;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Dates, StdCtrls;

type
MMW1N:CLASSINTERFACE TDateForm; ID=37;
var
DateForm: TDateForm;

implementation

MMW1N:CLASSIMPLEMENTATION TDateForm; ID=37;


end.

Aunque este codigo parece vagamente familiar a un programador de Delphi,


obviamente no podra compilarse. Estamos viendo la envoltura que el motor de
generacion de codigo de ModelMaker usara cuando expanda o genere una unidad
de codigo. Cuando ModelMaker genera una unidad, comienza por la parte supe-
rior de este codigo y emite lineas de texto mientras busca una de estas tres cosas:
texto plano, macros o etiquetas de generacion de codigo.
En este ejemplo, el texto plano puede encontrarse en la primera linea: u n i t .
ModelMaker emitira este texto exactamente tal y como es. El siguiente elemento
de la linea es una macro, < !UnitName ! >. Ya las comentaremos en profundi-
dad mas adelante, per0 baste ahora comprender que ModelMaker expandira la
macro en el sitio. En este caso, la macro representa el nombre la unidad, y sera ese
texto el que se emita.
Finalmente, un ejemplo de una etiqueta de generacion de codigo aparece direc-
tamente bajo la palabra clave t y p e :
MMW1N:CLASSINTERFACE TDateForm; ID=37;

En este caso, la etiqueta indica a ModelMaker que expanda la interfaz de clase


para TDate Form en este punto del codigo de la unidad.
Entonces, cuando se edite codigo en el Unit Code Editor, se vera una mezcla de
codigo gestionado por el desarrollador y codigo gestionado por ModelMaker.
Hay que tener cuidado cuando se edite este codigo para no perturbar el codigo
administrador por ModelMaker a no ser que se sepa lo que se esta haciendo. Es
algo analog0 a editar codigo en un archivo DPR gestionado por Delphi: se pueden
tener problemas si no se es cuidadoso. Aun asi, es aqui donde deberia aiiadirse
una declaracion de tip0 que no sea una clase (un tipo enumerado, por ejemplo). Se
manejaria como en Delphi, aiiadiendo la declaracion de tip0 en la seccion t y p e
de la unidad:
unit <!UnitName!>;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs, Dates, StdCtrls;

MMW1N:CLASSINTERFACE TDateForm; ID=37;


var
DateForm: TDateForm;

implementation

MMW1N:CLASSIMPLEMENTATION TDateForm; ID=37;


end.
En este ejemplo, ModelMaker emitira la declaracion de tipo tal y como se ha
escrito (corno texto plano) y despues expandira la declaracion de la clase
TDateForm.
ModelMaker ofrece herramientas dentro del Unit Code Editor para gestionar
metodos a nivel de unidad y existen unas facilidades significativas cuando se
tienen unidades del tipo de grandes bibliotecas de rutinas. Sin embargo, ahora que
se usa ModelMaker, se pueden usar sus potentes prestaciones de reingenieria
para convertir en objetos algunas de estas rutinas.

El editor Method Implementation Code Editor


El Method Implementation Code Editor de ModelMaker (vease figura 1 1.7) es
bastante diferente del Unit Code Editor. El editor ocupa dos tercios de la pantalla.
En este ejemplo, hemos aiiadido una propiedad ficticia llamada MyNewProperty
y permitido que ModelMaker genere un campo de estado y 10s metodos de acceso
de lectura y escritura. El metodo de acceso de escritura esta activado en el editor.
Junto a1 editor de codigo de la derecha se pueden ver dos ventanas interesantes.
La vista en arb01 de la parte superior es el explorador de codigo local. Desde aqui
se pueden gestionar variables y procedimientos locales. Justo debajo esta la Section
List; ModelMaker permite dividir el codigo dentro de la implernentacion de un
metodo en secciones. En parte, se trata de una comodidad organizativa, per0 lo
mas importante es que permite que ModelMaker controle secciones especificas de
codigo. A1 igual que ModelMaker puede poseer partes del modelo (corno el meto-
do de acceso a una propiedad generado automaticamente para una propiedad),
tambien puede poseer secciones de codigo dentro de un metodo. Lo mas habitual
es que esto ocurra cuando se escoge que ModelMaker genere el codigo de lectura
o escritura dentro del metodo de acceso a una propiedad. Fijese en que en este
ejemplo, las secciones primera, tercera y quinta, tienen un margen izquierdo mar-
cad0 con rayas rojas y blancas, que indica que pertenecen a ModelMaker. Las
secciones con el margen verde pertenecen a1 usuario. Cuando ModelMaker genera
este metodo, el codigo se emitira en el orden mostrado, una seccion tras otra.

ADVERTENCIA: Un gran inconveniente de escribir c6digo dentro de la


ventana Implementation de ModelMaker es que carece de todas las for-
mas de Code Completion que ofrece el IDE de Delphi.

La vista de diferencias
Como ya se comento, es facil caer en una situacion en que el modelo y 10s
archivos fuente pierdan su sincronizacion. Si se han editado tanto el modelo como
10s archivos fuente, no se puede simpleinente volver a generar 10s archivos a
partir del modelo, porque se sobrescribirian 10s cambios hecho en 10s archivos.
Del mismo modo, no se pueden volver a importar las unidades, pues seguramente
se eliminarian 10s cambios del modelo. Por suerte. ModelMaker ofrece una de las
herramientas de analisis de diferencias mas robustas que esisten. Cuando el mo-
delo haya perdido la sincronia, es hora de usar la pestaiia Difference (vease la
figura 1 1.8).
> laddE.lnkrr 6 - u a d
1
- - --
141 I Frncrtr and API Praicd
- -- ---- -
f

- 3.
4.7 * + , . C 1
SelC!dsrlnrcrurmnledaOos~IMM A
I
.b M A o a 3 n g B&a
Id 7 FModeLoadmu Bodeen
+. 1( ModePatComl lntw
7 FModtParICoun( I n l w ,
Id 9 GsrModelParlCwr lnteper,
+ .A MyNmPrqmty In(w
1 FMyPlswPrope~lyIntegef
GelMyNewPmpefly Inlepn
SetM:NcwPapetcomt aV& I
*. .a PrcgfersVaClc I n t q i w'
>
I . L-c 7 PLS 1 Irmt --
Figura 11.7. ModelMaker con la pestaiia de Irnplernentacion activa.

ModelMaker ofrece una gran variedad de maneras de ver diferencias. Se puede


ver una comparacion estandar de archivos de testo, diferencias en las marcas de
tiempo o incluso la comparacion de dos clases seleccionadas dentro del modelo.
Una vista muy usada es la que muestra la figura 1 1.8, una diferencia estructurada.
ModelMaker importa temporalmente el archivo fuente desde el disco (convirtien-
dolo en objetos del mismo mod0 que el modelo de codigo interno) y compara el
archivo importado con la misma unidad y clases en el modelo a1 nivel de objeto y
atributos mas que de texto. El resultado es una comparacion mucho mas rapida y
precisa. Fijese en 10s iconos que muestran diferencias en la vista en arbol de la
figura 11.8. Los <> en rojo indican que tanto el modelo como el archivo fuente
contienen el miembro de clase indicado ( b t n U I T e x t C l i c k en el ejemplo) pero
que las dos instancias son distintas. El codigo que cs distinto se muestra en 10s
controles de memo de la derecha. La + verde de la vista en arbol indica que el
miembro de clase indicado solo esistc en el modelo. no en el disco. La - am1
indica que el miembro de clase solo existe en disco, no en el modelo. Con esta
informacion, se puede decidir como proceder en el proceso de volver a sincronizar
el modelo. Una caracteristica muy practica es la capacidad de volver a importar
un metodo seleccionado (un lugar de toda la unidad) desde la vista Difference.

4 . . h ~ p , < ' $ ~ . ; ~72: p ;j1134 k)P.~!!i:~,o


d 3 1&S ~~EGmbpmen~\~oddMaker E~II\M~)
EX&SWMP bsk c~\Diivdopnrmwod~l~~
'3)C \DwebpmentWodelMaker ExpertsWMAP11 :
.
I: rtilic;.: defat~t v i r l b l l ~ t y : default
~~)C.\D~eb~ent\Mo&lMake~E~~lnW ~rccejur.
MAPII t z n U I T e ~ r C l l c k ' 5 c n d e r : pzcceduzo btnrlITcsst1ic't:Sendar:
rT.> C.\Devebpmenl\ModelMake~ExpertsWMAPII rLLe--r:- = = zr, a-z:=-=n=~z~z:.
;~,C,\Devebprnent\ModelMaker ExperlswMAP1l pzaccdure T ~ L S - W ~ ~ F I I X ~ ~ O : C E T Cp~rC wH ~~ d u r eTfzr-~PI1.xplo1CrTe.DX
begin Ljtuin
h?r C . \ D e v ~ n t \ M o d e l M a k e rExperlsWMAPIl ?la.~z*".e?2YL:
r?>C.\Dwebpnenl\ModelMakerEx~ert~WmP1l : r r ~ ~ ; ~ ~ i e 3 : - - . . ~ ~rrsize ull .
;pp. cnx, T~F~Y~;IzxFI~z~I~~F~!:I,.~.
I 0 C.\DevebprnenlWodelMaker ExpertsWtd4PII
' :: Classes ;:t
'5 = TlrrnMWlExpbrerTeslMam :r?.?CXT~'it-"i-ll.L 1xp:ezer
i ~ . Y ~ ; 1 ; I T r s r - ' ' 1 ~ i 1 - 13h:~3':5a

--
I - I p7t<~_--- I I - - I 1,-
Figura 11.8. La vista Difference de ModelMaker.

Este enfoque implica que es muy importante conocer cuando ha perdido la


sincronia el modelo, para que no se sobrescriban 10s cambios en el disco cuando
se vuelvan a generar 10s archivos fuente. Afortunadamente, ModelMaker ofrece
varias caracteristicas de seguridad que pueden prevenir esta situacion. Una de
estas es un D e s i g n C r i t i c . Si esta habilitado D e s i g n C r i t i c s , la vista
de mensajes de ModelMaker emitira una advertencia cuando cambie un archivo
del disco.

La vista Event Types View


ModelMaker permite la gestion de tipos de eventos en la pestaiia Events,
donde se pueden editar las declaraciones de tipos de eventos. Pero hay que tener
presente que aunque pueda existir un nuevo tipo de evento en el modelo de codigo
interno de ModelMaker, no existira en un archivo fuente hasta que se aiiada la
declaracion del tip0 de evento a una unidad. El mod0 mas facil de administrar este
proceso es arrastrar la declaracion de tip0 de evento desde la lista de la vista
Events a la lista Unit y dejarla caer sobre una unidad.

Documentacion y macros
ModelMaker puede ser muy practico para soportar 10s esfuerzos de documen-
tacion de software. Necesitaremos dominar un concept0 muy importante antes de
continuar (aunque no es muy complejo): dentro de ModelMaker, la documenta-
cion no equivale a 10s comentarios. No hay que tener miedo; se pueden hacer
cosas complicadas con 10s comentarios del codigo fuente, per0 hay que dar algu-
nos pasos para que ModelMaker emita (o importe) esos comentarios. Casi cual-
quier elemento del modelo (clases, unidades, miembros, simbolos de diagrama y
demas) pueden tener documentacion, per0 introducir documentacion para un ele-
mento no provocara que la documentacion aparezca automaticamente en el codi-
go fuente. El texto estara vinculado a1 elemento dentro del modelo, per0 hay que
provocar que ModelMaker genere un comentario en el codigo fuente que contenga
esa documentacion.

Documentacion frente a comentarios


Todo elemento en un modelo de codigo de ModelMaker puede tener dos tipos
de documentacion: un gran bloque de texto de documentacion estandar, y uno
pequeiio de una linea (vease figura 11.9). Estos textos pueden servir a varios
propositos en el context0 de ModelMaker y, como ya se comento, no se corres-
ponden directamente con comentarios en el codigo fuente, aunque este tip0 de
comentarios pueden generarse a partir de ellos.
En este ejemplo, una clase tiene ambos tipos de documentacion. Esta docu-
mentacion podria aparecer en un diagrama como una anotacion enlazada (puede
usarse cualquiera de las versiones de documentacion), o puede especificarse que
se use una de ellas o ambas como parte de 10s comentarios del archivo de codigo
fuente. Para hacer esto, se necesitara usar las potentes macros de ModelMaker y
modificar algunas de las opciones del proyecto. Por ahora no nos preocuparemos
de las macros (aceptaremos 10s valores predefinidos) y solo nos fijaremos en las
opciones del proyecto.
Accedemos a1 cuadro de dialog0 Project Options seleccionando Project
Options en el menu Options y hacemos clic sobre la pestaiia Source Doc
Generation. Aqui encontraremos muchas opciones relacionadas con la genera-
cion de comentarios de codigo fuente a partir de la documentacion de ModelMaker.
Para ver 10s comentarios de codigo fuente en accion, seleccionamos B e f o r e
Declaration en la seccion Method Implementation del grupo In Source
Documentation Generation. Ahora cualquier metodo que contenga documen-
tacion usara la macro predefinida para generar comentarios de codigo fuente.
,.------- -- - --
Cbss s v m g I
[Class 1
Documcnt~ton Symbol rl* 1 usual style I Hyperlinks I Cudom cmp*
Qwhn Tcs:T';.FIIxplrze: ~ r + : . l d e s a i e o =r suer--zed u k c s

ze-e - 5 t h e tazkm . . e c c l a c c d v i r h c r e a s r n g !.!cdeLKaAer


e?.~e:;3 .s: "plug-inl", us:?.q t h e XcdelXnne: +en
Tc:;s ;.GI. P x g ~ e cvhlc:?
~ us. a Trcc':ler c l any s c r t
to display the h i e r a r c h l c r ~ ncrucruz. c f t h e r r d a l
c r a Trea7.'ie-; + L i s t X a ~Crnfigczar1Cr.t cEn b.nafi5
f r c z chis ccq3n.n;. cF>

Figura 11.9. La pestaiia Documentation de un Class Symbol.

ModelMaker tambikn puede importar comentarios desde una unidad fuente y


asociar esos comentarios con 10s elementos apropiados del modelo de codigo.
Para hacer esto, se debe firmar 10s comentarios con una Document Import
signature (vease la pestaiia Source Doc Import del cuadro de dialog0
Project Options) e indicar a ModelMaker que lineas importar a1 modelo. Por
eso, si la irnplementacion del metodo tiene comentarios como 10s siguientes, se
puede indicar a ModelMaker que ignore las cinco primeras lineas y solo importe
el texto real del comentario:

TmyClass.DoSome thing
Returns: String
Visibility: Public
C o m e nt:
Este es el comentario real que queremos que importe
ModelMaker. Las cinco primeras lineas d e este bloque de
comentario no deberian importarse a 1 modelo. )

Cuando se configura ModelMaker para 10s comentarios de codigo fuente, es


importante prestar atencion a1 desplazamiento de comentarios. Puede suceder cuan-
do 10s parametros de importacion y exportacion no se corresponden por completo.
Por ejemplo, si la macro que controla la salida de comentarios en el codigo fuente
aiiade seis lineas para el comentario antes de aiiadir el texto de la documentacion,
per0 10s parametros de importacion solo eliminan cinco lineas, entonces cada
ciclo de importacion/generacion aiiadira una linea redundante de texto a1 comen-
tario.

Trabajo con macros


Las macros representan una de las prestaciones claves de ModelMaker: son
faciles de aprender, per0 dificiles de dominar. Una macro es un identificador que
representa un bloque de texto. Cuando ModelMaker se encuentra con una macro,
trata de sustituir el nombre de la macro con el texto que representa.
Ya hemos visto este proceso en funcionamiento en el Unit Code Editor:
< ! U n i tName ! > se sustituye en el momento de la generacion de codigo con el
nombre de la unidad que se esta generando. Este es un ejemplo de una macro
especifica de entidad que siempre es distinta segun la unidad que se genere. La
macro, U n i tName,esta predefinida, per0 el resultado depende del contexto.
ModelMaker incluye muchas macros predefinidas. Se pueden crear macros
propias de distinta complejidad (incluso macros anidadas) en la pestaiia Macros.
Tambien se pueden sobrescribir ciertas macros de expansion de documentacion
predefinidas. Por ejemplo, si se habilita la documentacion de implementacion de
metodos per0 no se proporciona una macro, ModelMaker usara su macro incor-
porada para generar 10s comentarios. Sin embargo, si se declara y define una
macro llamada MemberImpDoc, ModelMaker la usara cuando se generen co-
mentarios de metodos. (Conviene acudir a la ayuda electronics de ModelMaker
para ver una lista de macros que se pueden sobrescribir utilizadas para la genera-
cion de comentarios en el codigo fuente, bajo el tema "Documentation Macros" .)
Las macros no se usan solamente en tiempo de generacion de codigo. Tambien
se pueden expandir macros mientras que se escribe en el editor de codigo. En este
caso, se puede parametrizar una macro de manera que cuando ModelMaker trate
de expandirla se soliciten valores. Estos valores pueden introducirse en el texto
que se expande.

Reingenieria de codigo
Reingenieria es uno de esos terminos llamativos de la programacion de 10s que
constantemente se habla, per0 que significa cosas distintas para gente distinta. La
reingenieria es basicamente el proceso de mejorar el codigo existente in situ sin
alterar su comportamiento externo. No existe un unico proceso de reingenieria a1
que haya que acogerse, es simplemente el trabajo de tratar de mejorar el codigo in
situ sin provocar una gran perturbacion.
Muchos textos se dedican a este concepto, asi que simplemente prestaremos
atencion a las formas que tiene ModelMaker de ayudar a reorganizar el codigo.
Una vez mas, el modelo de codigo interno de ModelMaker tiene un papel impor-
tante (recordemos que desarrollar en ModelMaker no es simplemente desarrollar
codigo orientado a objetos, tambien es un proceso de desarrollo que es asistido
por la orientacion a objetos). Ya que todos estos elementos del modelo de codigo
se guardan internamente como objetos (objetos que tienen referencias cruzadas) y
ya que las unidades de codigo fuente se vuelven a generar completamente a partir
de este modelo cada vez que se decide generar el codigo, cualquier cambio en 10s
elementos del codigo se propagara a traves de las clases instantaneamente.
El ejemplo perfecto vuelve a ser una propiedad de clase. Si se tiene una propie-
dad llamada MyNewProperty con metodos de lectura y escritura (mantenidos
por ModelMaker y llamados G e t M y N e w P r o p e r t y y S e t M y N e w P r o p e r t y )
y se quiere renombrar la propiedad como M y p r o p e r t y , solo es necesario un
paso: renombrar la propiedad. ModelMaker se encargara del resto (10s metodos
de acceso se renombraran automaticamente como G e t M y P r o p e r t y y
S e t M y P r o p e r t y ) . Si la propiedad aparece en un diagrama, el diagrama se
actualizara automaticamente para representar el cambio. (Una advertencia:
ModelMaker no buscara automaticamente instancias deMyNewPropert y en el
codigo, habra que realizar una busqueda y sustitucion global dentro de
ModelMaker. Se trata de un ejemplo sencillo, per0 muestra el mod0 en que
ModelMaker simplifica las labores de reingenieria, como mover y renombrar ele-
mentos de codigo, donde ModelMaker se encargara de la mayoria de 10s detalles
en lugar del desarrollador. Estos son algunos casos especificos:
Renombrado sencillo: Esta tarea es bastante simple y ya la hemos comen-
tado, per0 su utilidad nunca se resaltara lo suficiente. Los cambios en el
nombre de un elemento del modelo de codigo se propagaran gracias a
ModelMaker a traves del modelo de codigo a todas las instancias de ese
elemento que conozca.
Reasignacion de clases: Este proceso absurdamente simple puede reali-
zarse de unas cuantas maneras distintas. Lo mas habitual es simplemente
arrastrar una clase en la vista Classes desde un nodo padre a otro (tam-
bien se puede hacer en un diagrama de clase arrastrando la flecha de gene-
ralizacion desde el antecesor anterior hasta el nuevo), y las clase tendra un
nuevo padre. Si la herencia esta restringida, ModelMaker actualizara
automaticamente 10s metodos heredados de la clase hija para que se corres-
pondan con las declaraciones de la nueva clase padre. La siguiente vez que
se genere el codigo, estos cambios apareceran automaticamente.
Mover clases entre unidades: Tambien es una labor sencilla. En la vista
Units, se arrastra la clase desde su lugar actual a la nueva unidad. Todo el
codigo relevante (declaraciones, implementaciones y comentarios) se vol-
vera a generar en la nueva unidad.
Mover miembros entre clases: En la reingenieria, este proceso se conoce
como "desplazamiento de prestaciones (o responsabilidades) entre obje-
tos". La idea es sencilla: a medida que el desarrollo progresa, se puede
descubrir que es mas apropiado mover ciertas responsabilidades
(implementadas como miembros de clase) a otra clase. Se puede hacer
mediante arrastrar y soltar. Seleccionamos 10s miembros de clase deseados
de entre las lista de miembros y 10s arrastramos sobre la nueva clase en la
vista Classes (manteniendo pulsada la tecla Mayus para mover en lugar
de copiar).
Conversi6n de miembros: Se trata de una de las prestaciones de
reingenieria de ModelMaker mas practicas. Hacemos clic con el boton
derecho del raton sobre un miembro en la Member List para que aparezca
el menu contextual que contiene las opciones y subopciones de menu
Convert To. Seleccionando una de estas subopciones podremos convertir
un miembro de clase ya existente de un tip0 de miembro a otro. Por ejem-
plo, si tenemos un campo privado llamado FMyInteger y queremos
convertirlo a un propiedad, ModelMaker crea automaticamente una pro-
piedad publica llamada My1 nteger, que lee desde y escribe en
FMyInteger.Del mismo modo, se puede convertir este campo en un
metodo (sera una funcion privada Ilamada MyInteger que devolvera un
entero).
Herencia restringida: En el cuadro de dialog0 del editor de metodos hay
una casilla de verificacion Inheritance Restricted. Cuando se inarca
esta casilla, ModelMaker no permite modificar la mayoria de 10s atributos
del metodo, ya que esos atributos se determinan de acuerdo con la
implernentacion del metodo sobrescrito en la clase antecesora. Si se cam-
bia la declaracion de un metodo en una clase antecesora, esos cambios se
aplicaran automaticamente a cualquier clase descendiente donde se haya
restringido la herencia del metodo que sobrescribe.
Si se tiene experiencia con la reingenieria (o se han usado las ultimas versiones
de JBuilder). esto puede no parecer un conjunto de herramientas de reingenieria
particularmente impresionante. Sin embargo, cuando se compara con lo que es
posible hacer solo en Delphi, se trata de un increible conjunto de posibilidades.
Ademas, la API OpenTools de ModelMaker ofrece acceso a la mayor parte del
modelo de codigo. Si no se esta contento con lo que ModelMaker ofrece tal y
como se instala, se pueden ampliar sus capacidades por uno mismo.

NOTA: Ademas, podemos decir que hay versiones beta (en el momento de
la elaboracion de este libro) de una version fbtura de ModelMaker que time
nuevas herramientas de reingenieria. La mayoria de ellas surgen &I libro
de Martin Fowler sobre la reingenieria, y son impresionantes.
Aplicacion de patrones de diseiio
ModelMaker se esfuerza a1 maximo en su soporte de patrones de diseiio.
ModelMaker proporciona la comodidad de aplicar una implementacion patron
con un simple clic de raton. Segun el patron escogido, puede tener lugar una gran
variedad de acciones. Algunos patrones muestran un asistente antes de aiiadir
codigo y otros simplemente aiiadirhn sus miembros a la clase seleccionada. Como
ya se comento, estos miembros nuevos pertenecen a ModelMaker y como resulta-
do es facil actualizarlos. Ademas, si se decide eliminar la aplicacion del patron,
ModelMaker eliminara cualquier miembro de clase que haya aiiadido para ese
patron. Como ejemplo, podemos fijarnos en el patron Singleton. Supongarnos que
tenemos una clase y solo queremos que como mucho exista una instancia de ella.
Esta es la clase de muestra:
tYPe
TOneTimeData = class (TObject)
private
FGlobalCount: Integer;
procedure SetGlobalCount(const Value: Integer);
public
property GlobalCount : Integer read FGlobalCount write
SetGlobalCount;
end ;

El patron Singleton obliga a1 uso de un punto de entrada unico (la funcion de


clase I n s t a n c e en la implementacion de ModelMaker de este patron, como se
vera) para tener acceso a la unica instancia de la clase. Si la instancia no existe
aun, se creara y devolvera. De no ser asi, se devolvera la instancia ya existente.
Ya que I n s t a n c e es el punto de entrada, se evitara el uso de c r e a t e para esta
clase. Una vez que se aplica el patron Singleton a la clase en ModelMaker, tendra
este aspecto:
tYPe
TOneTimeData = class (TObject)
private
FGlobalCount: Integer;
procedure SetGlobalCount(const Value: Integer);
protected
constructor CreateInstance;
class function AccessInstance (Request: Integer) :
TOneTimeData;
public
constructor Create;
destructor Destroy; override;
class function Instance: TOneTimeData;
class procedure ReleaseInstance;
property GlobalCount: Integer read FGlobalCount write
SetGlobalCount;
end:
No vamos a proporcionar las implementaciones de 10s metodos, se pueden ver
aplicando el patron como prueba o analizando el codigo fuente del ejemplo
PatternDemo.

ADVERTENCIA:El cbdigo usado por ModelMaker para implementar 10s


patrones Singleton se basa en el interesante uso de una constante dentro de
un m&odo para simular datos por clase. Sin embargo, este codigo fallad al
compilarse a no ser que se habilite la opci6n de compilador Assignable
Tvned Constants Del~hi.aue de manera ~redeterminadaesth inhabilitada.

Patrones de disefio
Mientras que 10s programadores se concentran en la irnplementacion de
clases especificas, 10s diseiiadores se centran m b en conseguir que clases y
objetos distintos hncionen juntos. Aunque es dificil ofrecer una definici6n
n r e ~ i c sAn1 A i c n i i ~AP c n f k w a r ~W
yIWWIUCL U W I U a o W Y V Y W UVLCVIUIW,
mm m c ~ m ~C a t r s t a AP
iP
Y W D W I I W I U UV % & U C U U W
11 nrrran;va&An rle
I U V16LUILOWfiVY U W A U
11

estructura global de un programs.


Contemplando distintas soluciones de disefio de la gente ante distintos pro-
blemas, se pueden ver elementos comunes y ciertas similitudes. Un patron
es el conocimiento de un disefio comun como estos, expresado de un mod0
esthdar y suficientemente abstract0 como para ser aplicable a un cierto
numero de situaciones distintas. Los patrones de diseilo tienen que ver con
. . . . . .. - - . .. . . ..- . .
el reciclaje de disefios ms que de codlgo. Aunque la soluci6n comcada de
.a

un patron puede s e ~der inspiration para el programador, el foco real esth


en el diseilo: incluso aunque se podria tener que volver a escribir el cbdigo,
comenzar con un diseiio claro y probador ahorrarh una cantidad de tiempo
considerable. Los patrones de disefio no tratan acerca de 10s bloques cons-
tructivos primitives (corno m a tabla hash o m a lista mhida) o proble-
mas especificos de un dominio (como hacen 10s pattoqes de d i s i s ) .
El creador reconocido del movimiento del patr6n no fie un diseiIador de
sofhare sino un arquitecto, que se fi.6 en el uso de patrones en las cons-
trucciones. "Cada pat& describe m problema que aeoritece une\. y otra vez
en el entorno, y despues describe el nucko de la s o l u c b a em problema, de
un modo tal que pueda usarse la solucih un milion de veces miis, sin volver
a hacer lo mismo dos veces." Erich Gamma,Richard Helm, Raiph Johnson
y John Vlissides escribieron el iibro qub dio pie al movimiento de pajrones
en el rnundo del software: "Design Patterns: Elements of Reusable Object-
Oriented Software (Addison-Wesley, 1995)". Zos autores suelen indicarse
como "Gamma et al." pero tambih se llama el Grupo dk 10s Cuatro, Gang
of Four o GoF. Por eso el libro suele r~ferirsecoloquialmmte como " l ~ r o
GoF". El libro describe el c0m-epi.o& 10s patrones softovare, W c a p n
modo p m e s c r i b i r l o s y o h c e un catidogo de 23 patrones divididos
en tres grugos: sreativos, estructurales y de comportadento. La rnayoria
de 10s patrones de libro e s t b implernentados en C-H, y algunos en Smalltalk,
aunque generalmente se abstr& el lenguaje y son i&al&ente aplicables en
Java o Delphi. La estructura central de un patron es la siguiente:
- Cl ,,A.L,,
GI UUUUIG
A-I ,
,,
,L
, ,,
,
,A
,
,:
UCI pauuu ~3 U I I ~ VLCUILG,
I
-
a
,.
.
pad a
.
, "
~ U 3~
,
,.A, L
.. ", ,,c
, :
,
G ~ U G WU ~ ~ IGG LI G I C L I L I ~

al patr6n cuando se hable con otros programadores y diseiladores.


El problem describe cufindo aplicar UQ patron, incluyendo eventual-
me& el contexto y las condiciones.
La solucibn describe la parte de elementos del diseiio y sus relaciones.
No se bata de una implernentaci6nysolo de una descripcion abstracta
de responsabilidadesy colaboracionesde clase.
Las consecuencias son 10s resdtados y acuerdos de aplicar un patron,
incluidas limitaciones de espacio y tiemPo.
patrones desde la
perspecuva ae utxpru. ~m emoargo, nryl apareciao muchos articulos en
revistas be Delphi [como Dslphi Wormant y The Delphi Magazine). Los
patroms GoF clhsicos han sido fuente de inspiracirin para muchos articu-
105, junto con la discusi6n M l a d a de bs patrones disponibles con la
docamentircib de ModelMaker.
No siempre es posible estar de acuerdo con la irnplementaci6n Delphi de
algunos paones esthdar. De hecho, solemos tender a centramos en el
diseflo snbyacetrte y eomo mantenerlo el pasar de la implementation GoF
(m-& en C++ o Java) a h l ~ h mientras
i aue se amovechan las
prcstaciones IS i-
w,qrrew&b :s

dores y se qrenderh modos mejores de aplicar ttcnicas de orientation a


objeta (em particular la en~apsulaci6ny un acoplamicmw reducido).
Cotho mgerencia W,tengamm a wenta gue la qsayor parte de 10s patro-
near se impleinm@ui mjor en Delp& mediante interfaces que con clases
+orno suele.hacerModelMslker, de h e r d o con el enfoque clbico).

ModelMaker ofrece implementaciones de varios patrones mas, como Visitor,


Observer, Wrapper, Mediator y Decorator. Estan incrustados en ModelMaker
para aplicarse de un mod0 especifico, y algunas de las implementaciones son
mejores que otras. Esto ha sido un tema controvertido entre algunos desarrolladores
y por eso (entre otras cosas) ModelMaker soporta otros sistemas de aplicacion de
patrones: las plantillas de codigo. Este enfoque permite la creacion y persona-
lizacion en la parte del desarrollador. Sin embargo, no hay que olvidar el extenso
soporte de ModelMaker para patrones; son bastante buenos y ofrecen una
implernentacion Delphi solida, fija y que funciona de estos problemas habituales.

Plantillas de codigo
Otra potente prestacion mas en ModelMaker (que parece perdida, escondida
entre miles de otras caracteristicas) son las plantillas de codigo, una tecnica que
se puede usar para crear implementaciones propias de patrones de diseiio. Las
plantillas de codigo son como una diapositiva de parte de una clase que puede
aplicarse a otra clase. En otras palabras, se trata de un conjunto de miembros de
clase, guardados en una plantilla que puede aiiadirse a otra clase, de golpe. Aun
mejor, estas plantillas pueden parametrizarse (corno las macros) para que cuando
se apliquen a una clase aparezca un cuadro de dialogo solicitando rellenar ciertos
valores, que despues se aplican como parte de la plantilla. Un ejemplo es una
propiedad matriz. Declarar una propiedad matriz es sencillo en ModelMaker,
per0 implementar completamente una requiere varios pasos: no solo se necesita la
propiedad matriz en si, si no tambien una T L i s t o clase descendiente que con-
tenga 10s elementos de la matriz y un sistema para proporcionar un recuento de
10s elementos almacenados. Incluso para este ejemplo tan sencillo, se requiere
algo de trabajo para conseguir que la propiedad de matriz quede lista y en funcio-
namiento. Aqui entra en accion la plantilla de la propiedad matriz. Abrimos un
modelo en ModelMaker (o creamos un nuevo modelo y aiiadimos un descendiente
de T O b j e c t ) y escogemos una clase a la que queramos aiiadir la nueva propie-
dad matriz. Hacemos clic con el boton derecho sobre la Member List y seleccio-
narnos Code Templates. Ahora deberia aparecer una barra de herramientas
flotante llamada Code Templates (fijese en que se trata de la misma barra de
herramientas disponible en la pestaiia Patterns). Hacemos clic sobre el boton
Apply Array Property Template para abrir el cuadro de dialogo Code Template
Parameters. Contiene una lista de elementos que se puede especificar para la
plantilla que se va a aplicar, como muestra la figura 11.10. Se puede resaltar
cualquier elemento de la columna de la izquierda y pulsar la tecla F2 para editar
el valor de ese parametro. Aceptamos 10s valores predeterminados y hacemos clic
sobre OK. La clase deberia contener ahora 10s siguientes miembros:
private
FItems : TList;
protected
function GetItemCount: Integer;
function GetItems (Index: Integer) : TObject;
public
property Itemcount: Integer read GetItemCount;
property Items [Index: Integer] : TObject read GetItems;

Se puede comprobar lo flexible que resulta esta tecnica. Es facil implementar


otras tareas habituales (corno listas de tipado fuerte) e implementaciones propias
de 10s patrones de diseiio, como vamos a ver.
I Desapllcn - 1
name nl array prope~ty
TOblrct type of anray p~operty
l terncount Method returnmg I Items
Fltems TLlst F~eldstormg Items

Figura 14.10. El cuadro de dialogo Code Template Parameters de ModelMaker.

Para crcar una plantilla de codigo propia, comenzaremos con una clase exis-
tente que ya tenga 10s miembros que se desean convertir en una plantilla. Selec-
cionamos esa clase y, a continuacion, en la Member List, seleccionamos 10s
micmbros que deseamos usar (puede tratarse de cualquier tip0 de miembro). Ha-
cemos clic con el boton derecho sobre la Member List y seleccionamos Create
Code Template. Aparecera el cuadro de dialogo Save Code Template. Es
muy parccido a1 cuadro de dialogo Guardar como estandar (y se especifica
donde guardar la plantilla), per0 se puede especificar el mod0 en que se desea que
aparezca la plantilla. Especificamos un nombre para la plantilla y la pagina de la
paleta dc plantillas en que se desea que aparezca. Prestamos atencion a1 mensaje
de confirmacion resultante; se puede modificar la imagen de la paleta si se desea.
Ahora la nueva plantilla estara disponible en la paleta de plantillas, de manera
que se pucde aiiadir esta plantilla a cualquier clase. Para parametrizar la planti-
lla, hay quc modificar el archivo PAS que se creo cuando se guard6 la plantilla.
Por ejemplo, esto es parte del archivo A r r a y P r o p -List .p a s usado por la
plantilla Array Property:
unit ArrayProp-List;

/ / D E F I N E M A C R O :I t e m s = n a m e o f a r r a y p r o p e r t y
//DEFINEMACRO: TObject=type of a r r a y p r o p e r t y
//DEFINEMACRO:IternCount=Method r e t u r n i n g # i t e m s
//DEFINEMACRO:FItems=TList F i e l d s t o r i n g items

TCodeTemplate = class (TObject)


private
< ! FItems!>: TList;
protected
function Get<!ItemCount!>: Integer;
function Get<!Items!> (Index: Integer): <!TObject!>;
public
property <!Itemcount!>: Integer read Get<!ItemCount!>;
property <!Items!> [Index: Integer] : <!TObject!> read
Get<! Items ! >;
end;

Fijemonos en las lineas que comienzan con / /DEFINEMACRO.Es aqui donde


se declaran 10s parimetros; apareceran en el cuadro de dialog0 Code Template
Parameters que se vio anteriormente. Cada linea es un par nombre 1 valor: el
elemento a la izquierda de = es el valor editable, y el elemento que esta a la
derecha es la descripcion que se puede proporcionar para explicar el parametro.
Despues de proporcionar una lista de parametros, pueden usarse como macros
en el codigo de la plantilla. Existen unas lineas en el ejemplo que son como estas:
property < ! Items!>[Index: Integer] : <!TObject!> read
Get<!Items!>;

Cuando se aiiade esta propiedad a una clase como parte de la plantilla, las
macros (cosas como < ! ~te m s ! >) seran sustituidas por el valor del parametro
apropiado. De este modo, se pueden usar parametros para personalizar en profun-
didad las plantillas de codigo.

Detallitos poco conocidos


Esta es una lista de interesantes prestaciones que podrian desearse examinar:
Rethink Orthogonal: Se pueden modificar las lineas rectas y diagonales
predefinidas del Diagram Editor para que sean lineas ortogonales si se
pulsa Control-0.
Visual Styles Manager: Este administrador (disponible en el menu de
metodo abreviado de la vista de diagrama, bajo Visual style>Style mana-
ger) requeriria una seccion por completo. Tomese su tiempo en estudiarlo.
Se puede definir una amplia variedad de estilos visuales relacionados jerar-
quicamente para 10s simbolos de diagramas y aplicarlos sobre la marcha.
Design Critics: Los criticos de diseiio son una impresionante caracteristi-
ca de analisis de ModelMaker. Se trata de pequeiios lectores de pruebas
que se ejecutan en segundo plano, verificando el codigo.
Creational Wizard: Se trata de otra preciosa herramienta de automacion
mas para el ocupado programador de Delphi. El asistente Creational
Wizard (disponible mediante el boton Wizards de la Member List) com-
prueba el modelo para 10s miembros de clase que necesitan ser instanciados
o liberados y les aiiade el constructor o destructor apropiado.
L a API OpenTools: A1 igual que la API Tools de Delphi, esta caracteris-
tica de ModelMaker permite la creacion de aiiadidos expertos para
ModelMaker .
De COM

Durante aiios, desde la aparicion de Windows 3 .O, Microsoft ha prometido que


su sistema operativo y su API se basarian en un modelo de objetos real en lugar de
en funciones. De acuerdo con lo esperado, Windows 95 (y mas tarde Windows
2000) deberian haberse basado en este enfoque tan revolucionario. No sucedio
nada de esto, per0 Microsoft continuo introduciendo COM (Component Object
Model, modelo de objetos componentes), construyendo el envoltorio de Windows
95 sobre este modelo, promoviendo la integracion de aplicaciones con COM y sus
tecnologias derivadas (como Automation) y hasta llegar a presentar COM+ con
Windows 2000. Ahora, poco despues de la aparicion a1 completo de 10s cimientos
necesarios para una programacion sobre COM de alto nivel, Microsoft ha decidi-
do pasar a una nueva tecnologia central, parte de la iniciativa .NET. Parece que
COM no se encontraba realmente preparado para la integracibn de objetos deta-
llados, aunque tuvo exito a1 proporcionar una arquitectura para integrar aplica-
ciones u objetos mayores.
A lo largo de este capitulo construiremos nuestro primer objeto COM, pres-
tando atencion a 10s elementos basicos para permitir la mejor comprension del
papel de esta tecnologia sin profundizar demasiado en 10s detalles. Continuare-
mos comentando Automation y el papel de las bibliotecas de tipos, y veremos
como trabajar con 10s tipos de datos de Delphi en 10s servidores y clientes de
Automation.
Por ultimo, exploraremos el uso de objetos incrustados, con el componente
Olecontainer y el desarrollo de controles ActiveX. Tambien hablaremos de las
tecnologias COM sin estado (MTS y COM+) y de algunos otros conceptos avan-
zados como el soporte a la integracion con .NET ofrecido por Delphi 7.
Este capitulo trata 10s siguientes temas:
El concept0 de COM.
COM, GUID y factorias de clases
Las interfaces de Delphi y COM.
Las clases de soporte a COM de la VCL.
Creacion y uso de servidores Automation.
Uso de bibliotecas de tipos.
El componente Container
Creacion de un ActiveX y un ActiveForm.
Presentacion de COM+.
COM y .NET en Delphi 7.

Una breve historia de OLE y COM


Parte de la confusion relativa a la tecnologia COM proviene de que Microsoft
ha utilizado diversos nombres para ella en sus primeros aiios por motivos de
mercado. Todo comenzo con Object Linhng and Embedding (OLE), que era una
extension del modelo DDE (Dynamic Data Exchange). Usar el portapapeles per-
mite copiar datos en bruto, y usar DDE permite conectar partes de dos documen-
tos. OLE permite copiar datos desde una aplicacion de servidor a una aplicacion
cliente, junto con informacion referente a1 servidor o una referencia a informacion
almacenada en el registro de Windows. Los datos brutos podrian copiarse junto
con el enlace (se incrusta el objeto, embedding) o mantenerse en el archivo origi-
nal (enlace del objeto, linhng). Ahora, 10s documentos OLE se llaman documen-
tos activos. Microsoft actualizo OLE como OLE 2 mediante su reimplementacion
no solo como una ampliacion de DDE sino aiiadiendo tambien nuevas caracteris-
ticas, como OLE Automation y OLE Controls. El siguiente paso fue crear la
envoltura de Windows 95 mediante tecnologia e interfaces OLE y renombrar a
continuacion 10s controles OLE (que tambien se conocian como OCX) como con-
troles ActiveX, modificando la especificacion para permitir controles de poco
peso preparados para su distribucion a traves de Internet. Durante un tiempo,
Microsoft publicito 10s controles ActiveX como adaptados para Internet, per0 la
idea jamas fue completamente aceptada por la comunidad de desarrolladores (a1
menos no como "adaptados" para el desarrollo para Internet).
A medida que esta tecnologia se iba extendiendo y cobrando importancia para
la plataforma Windows, Microsoft volvio a cambiar el nombre a OLE, despues a
COM y finalmente a COM+ para Windows 2000. Estos cambios en el nombre
solo estaban relacionados en parte con cambios tecnologicos y basicamente se
debian a propositos de mercado.
Basicamente, COM es una tecnologia que define un mod0 estandar para comu-
nicar un modulo cliente y un modulo servidor a traves de una interfaz especifica.
En este caso, modulo indica tanto una aplicacion como una biblioteca (una DLL);
10s dos modulos pueden ejecutarse en el mismo ordenador o en maquinas distintas
conectadas mediante una red. Son posibles muchas interfaces, segun el papel del
cliente y el servidor, y se pueden aiiadir nuevas interfaces para propositos especi-
ficos. Estas interfaces las implementan objetos de servidor. Un objeto de servidor
suele implementar mas de una interfaz, y todos 10s objetos de servidor tienen unas
cuantas prestaciones comunes, ya que todos deben implementar la interfaz
IUnknown (que se corresponde con la interfaz IInterface especifica de Delphi).
La buena nueva es que Delphi es completamente conforme con COM. Cuando
aparecio Delphi 3, su implementation de COM era mucho mas sencilla y estaba
mas integrada en el lenguaje que C++ u otros lenguajes de la epoca, hasta el punto
de que incluso programadores del equipo de investigacion y desarrollo de Windows
comentaron que deberian haber creado COM del mod0 en que lo hizo Delphi. Esta
simplicidad se deriva principalmente de la incorporacion de tipos de interfaz en el
lenguaje Delphi. (Las interfaces tambien se usan de un mod0 similar para integrar
Java con COM en la plataforma Windows.)
Como ya se ha comentado, la intencion de las interfaces COM es la comunica-
cion entre modulos de software, que pueden ser archivos ejecutables o DLL.
Implementar objetos COM en archivos DLL suele ser mas sencillo, ya que en
Win32 un programa y la DLL que utiliza ocupan el mismo espacio de direcciones
de memoria. Esto significa que si el programa pasa una direccion de memoria a la
DLL, la direccion sigue siendo valida. Cuando se usan dos archivos ejecutables,
COM debe realizar mucho trabajo interno para permitir que las dos aplicaciones
se comuniquen. Este mecanismo se llama marshalling (que, para ser precisos,
tambien es necesario para las DLL si el cliente es multihilo). Hay que hacer notar
que una DLL que implementa objetos COM se describe como un servidor en
proceso, mientras que cuando el servidor es un ejecutable independiente, se llama
servidor fuera de proceso. Sin embargo, cuando las DLL se ejecutan sobre otra
maquina (DCOM) o dentro de un entorno de servidor (MTS), tambien son fuera
de proceso.

Implernentacion de IUnknow
Es importante que revisemos en primer lugar algunos conceptos basicos de
COM. Todo objeto COM debe implementar la interfaz IUnknown, tambien deno-
minada llnterface en Delphi, para usar interfaces que no sean de tipo COM. Esta
es la interfaz base de la que heredan todas las interfaces de Delphi, y Delphi
proporciona un par de clases diferentes con implementaciones de IUnknownl
I I n t e r f a c e listas para utilizar, como T I n t e r f a c e d O b j e c t y
TComOb j e c t . La primera sc puede utilizar para crear un objeto interno no
relacionado con COM, mientras que la segunda se utiliza para crear objetos que
pueden ser exportados por servidores. Como ya se vera, existen varias clases mas
que heredad de TComOb j e c t y proporcionan soporte para mas interfaces, que
son requeridas por servidores Automation o controles ActiveX.
La interfaz IUnknown tiene tres metodos: AddRe f , Re l e a s e y
Q u e r y 1n t e r f a c e . Aqui e s t i la definicibn de lainterfaz l ~ n k n o w n
(extraida
de la unidad System):
type
IUnknown = i n t e r ace
['{OOOOOOOO-0000-0000-COOO-000000000046}']
f u n c t i o n Q u e r y I n t e r f a c e ( c o n s t I I D : TGUID;
out O b j ) : Integer; stdcall;
f u n c t i o n -AddRef: I n t e g e r ; s t d c a l l ;
f u n c t i o n -Release: I n t e g e r ; s t d c a l l ;
end;

Los metodos -AddRef y -Release se utilizan para implementar el recuento


de referencias. El metodo ~ u e r Iyn t e r f ace controla la information de tipo y
compatibilidad de tipos de 10s objetos.

rA: En el codigo anterior, se puede ver un e . :mplo - de un p a r h e t r o


out,, un p a r h e t r o devuelto desde dl mCtodo a1 programa
prc que lo ilama per0
. , I . . ..- m *- . - --. -1
sin ningun valor inicial pasado por el programa que llama a1 metodo. Los
parametros out se han aiiadido a1 lenguaje Delphi especificamente para el
soporte de COM, per0 pueden utilizarse en una aplicacion normal, ya que
en ciertas circunstancias esto hace que el paso de parkmetros sea mas efi-
,--
GU
1---,
-1
\C;UIIIU ---- A - :-&--A7 ---- r;aut;nas
CIJ GI caw UG ~nr~rratics,
-,A ---- -. --*.2--- A:---:^^^\
y rrlau~r;cs
ulnarmr;as). rF--
1 am-
b i h es importante seiialar que aunque la definition del lenguaje Delphi
para el tipo de interfaz esti diseiiada para tener compatibilidad con COM,
las interfaces de Delphi no requieren COM. De hecho, ya hemos construido
anteriomente en el libro algiin ejemplo basado en interfaces sin soporte
para COM.

Normalmente, no sera necesario implementar estos metodos, ya que se pueden


heredar de una de las clases de Delphi que ya 10s soportan. La clase mas impor-
tante es TComOb j e c t , definida en la unidad ComObj. Cuando se construye un
servidor COM, generalmente se hereda de esta clase.
Esta clase implementa la interfaz IUnknown (proyectando sus mktodos sobre
O b j A d d R e f , O b j Q u e r y I n t e r f a c e , y O b j R e l e a s e ) y la interfaz
ISupportErrorlnfo (mediante el metodo ~ n t e racesupports~rror~nf
f 0).
La implernentacion del recuento de referencias para la clase TComOb ject so-
portar seguridad de hilos, ya que en lugar de utilizar los procedimientos Inc y
Dec, el codigo utiliza las funciones de la API InterlockedIncrement e
InterlockedDecrement. La implernentacion del metodo Release de
TInt erfacedObject destruye el objeto cuando ya no hay m g referencias a
el. La clase TComObject hace lo mismo. Tambien debemos recordar que cuan-
do se utilicen variables de interfaz para referirse a 10s objetos (incluidas variables
COM), Delphi automaticamente aiiade llamadas de recuento de referencias a1
codigo compilado, lo que destruye inmediatamente 10s objetos a 10s que no hay
referencias. Finalmente, hay que fijarse en que el papel del metodo
QueryInterf ace tiene dos vertientes:
QueryInt erface se utiliza para la comprobacion de tipos. El progra-
ma puede formularle la siguiente pregunta a un objeto: ~ E r e del
s tipo que
me interesa? iImplementas la interfaz y 10s metodos especificos que quiero
llamar? Si la respuesta es no, el programa puede buscar otro metodo, qui-
zas preguntando a otro servidor.
Si la respuesta es si, Query Interface normalmente devuelve un pun-
t e r ~a1 objeto, utilizando su parametro de referencia de salida (obj ) .
Para entender la funcion del metodo QueryInter face,es importante tener
en cuenta que un objeto COM puede implementar varias interfaces, a1 igual que la
clase TComObject.Cuando se llama aQueryInterface,se debe pedir uno
de las interfaces posibles del objeto, utilizando el parametro TGUID.
Ademas de la clase TComObje c t, Delphi incluye mas clases COM
predefinidas. Esta es una lista de las clases COM mas importantes de la VCL de
Delphi, que usaremos profusamente mas adelante:
TTypedComObject: Definida en la unidad C omOb j , hereda de
TComObject e implementa la interfaz IProvideClasslnfo (ademas de
las interfaces IUnknown e ISupportErrorlnfo ya implementadas por la
clase basica).
TAutoObject: Definida en la unidad ComObj,hereda de TTypedCom-
Ob ject y tambien implementa la interfaz IDispatch.
TActiveXControl: Definida en la unidad AxCt rls,hereda de TAuto-
Ob j ect e implementa varias interfaces (IPer~i~tStreamInit, IPersist-
Storage, IOleObject e IOleControl, por citar unas cuantas).

ldentificadores globalmente unicos


El metodo QueryInterface tiene un parametro del tipo TGUID.Este tipo
representa un identificador unico que identifica alguna clase de objeto COM (en
cuyo caso el GUID se llama CLSID); interfaces (en cuyo caso se vera el tkrmino
IID): y otras entidad COM y del sistema. Cuando se quiere saber si un ob-jeto
soporta una interfaz especifica, se pregunta a1 ob-jeto si implementa la interfaz
que tiene un determinado identificador (que en el caso de las interfaces COM
prcdefinidas esta establecido por Microsoft). Para indicar una clase especifica, se
usa otro ID (o CLSID). El Registro de Windows guarda este identificador (CLSID),
con indicaciones sobre la DLL o el archivo e.jecutable relacionados. Los
desarrolladores de un servidor COM definen el identificador de clase.
Todos estos identificadores se conocen como 10s GUID, o identificadores
globalmente unicos. Si cada desarrollador usa un numero para indicar su propio
servidor COM, jcomo podemos estar seguros de quc estos valores no estan dupli-
cados? La respucsta corta es que no podemos. La verdadera respuesta es que el
GUID cs un numero tan grande (con 16 bytcs, o 128 bits, jque implica un numero
con 38 digitos!) que es casi imposible conseguir dos niimeros aleatorios que ten-
gan el mismo valor. Ademas, 10s programadores pueden usar la llamada especifi-
ca CoCreateGuid dc la API (directamente o a travds de su cntorno de desarrollo)
para conseguir un GUID valido que refle~ealguna informacion del sistema.
En partc, 10s GUID creados en equipos con tarjetas de rcd tienen la garantia de
ser unicos, porque las tarjetas de rcd conticnen numeros de serie unicos que for-
man una basc para la creacion dc GUID. Los GUID creados en equipos con
identificadores de la CPU (como las Pentium 111) tambiCn pueden tener la garan-
tia dc scr unicos, incluso sin tar-jcta de red. Aunque no cxista un identificador de
hardware unico; es poco probable que 10s GUID sc repitan.
- - -- . -- - -
ADVERTENCIA: Ademas de tener cuidado de no copiar el GUID del
programa de otra persona (que puede producir dos objetos COM totalmente
diferentes que usen el mismo GUID), nunca se debe inventar un identificador
propio introduciendo una secuencia casual de numeros. Para evitar cual-
quier problema, simplemente hay que pulsar Control-Mayiis-G en el editor
de Delphi y se obtendra un nuevo GUID definido correctamente y unico.

En Delphi, cl tip0 TGUID (definido en la unidad System) es una estructura de


registro, que es bastantc extraiia, per0 necesaria para Windows. Gracias a la
magia del compilador de Delphi, tipicamente preparado para simplificar las ta-
reas mas tediosas o que requieren mas tiempo, se puede asignar un valor a un
GUID usando la notacion hexadecimal estandar guardada dentro de una cadena,
como en este fragment0 de codigo:
cons t
Class-ActiveForml: TGUID = ' [1AFA6D61-7B89-llDO-98DO-
444555540000)';

Tambiln se puede pasar una interfaz identificada mediante un IID donde se


necesita un GUID, y una vez mas, Delphi estraera automaticamente el IID
referenciado. Si tenemos que crear un GUID manualmente sin el entorno de Delphi,
sencillamente podemos llamar a la funcion de la API de Windows CoCreate-
Guid, como se muestra en el ejemplo NewGuid (vease la figura 12.1). Este
ejemplo es tan simple que no mostramos su codigo.

Figura 12.1. Un ejemplo de 10s GUID generados por el ejemplo NewGuid. Los valores
dependen del ordenador en el que se ejecute el programa y del momento de
ejecucion.

Para controlar 10s GUID, Delphi ofrece la funcion GUIDToSt ring y su


opuesta St ringToGUI D. Tambien se pueden utilizar las funciones correspon-
dientes de la API de Windows, como StringFromGuid2;per0 en este caso, se
debe utilizar el tipo WideString en lugar del tipo de cadena. Siempre que se
utilice COM, se debe usar el tipo WideString, a menos que se utilicen las
funciones de Delphi que realizan automaticamente las conversiones requeridas.
Cuando se necesite sortear las funciones de Delphi que pueden llamar directamen-
te a las funciones de la API de COM, se puede usar el tipo PWideChar (punte-
ros a matrices de caracteres amplios terminadas en cero), o convertir el tip0
WideString en PWideChar (del mismo mod0 en que se convierte una cadena
al tipo PChar cuando se llama a la API de bajo nivel de Windows).

El papel de las fabricas de clases


Cuando registramos el GUID de un objeto COM en el Registro, podemos
utilizar una funcion especifica de la API para crear el objeto, como por ejemplo
CreateComObject:
function CreateComObject (const ClassID: TGUID) : IUnknown;

Esta funcion de la API busca en el Registro, encuentra el servidor que registra


el objeto con el GUID dado, lo carga, y, si el servidor es una DLL, llama al
mCtodo DLLGetClassOb j ect de la DLL. Esta es una funcion que todo servi-
dor de proceso debe proporcionar y exportar:
f u n c t i o n DllGetClassObject (const CLSID, IID: TGUID;
var O b j ) : HResult; stdcall;
Esta funcion de la API recibe como parametros la clase y la interfaz solicita-
das, y devuelve un objeto en su parametro de referencia. El objeto devuelto por
esta funcion es una fabrica de clases.
Como su nombre sugiere, una fabrica de clases es un objeto capaz de crear
otros objetos. Cada servidor puede tener varios objetos. El servidor expone una
fabrica de clases para cada uno de 10s objetos COM que puede crear. Una de las
muchas ventanas del enfoque simplificado de Delphi a1 desarrollo COM es que el
sistema puede proporcionar una fabrica de clase en lugar del programador Por
este motivo, no fue necesario aiiadir una fabrica de clase personalizada a1 ejem-
plo. La llamada a1 metodo CreateComObject de la API no termina con la
creacion de la fabrica de clases. Tras recuperar l a fabrica de clases,
CreateComObject llama a1 metodo CreateInstance de la interfaz
IClassFactory. Este metodo crea el objeto solicitado y lo devuelve. Si no se
produce ningun error, este objeto se convierte en el valor de retorno de la API de
CreateComObject.
Mediante este mecanismo, (incluyendo fabricas de clases y la llamada a
DLLGetClassObject), resulta muy sencillo crear objetos COM. A1 mismo
tiempo, Crea teComObje ct es simplemente una llamada a una funcion que
tiene un comportamiento mas complejo que el que aparenta a simple vista. Lo
bueno de Delphi es que ese mecanismo complicado de COM lo lleva a cab0
automaticamente el sistema en tiempo de ejecucion (se encarga de ello la RTL).
Para cada clase COM basica de la VCL, Delphi define tambien una fabrica de
clase. Las clases de fabricas de clases forman una jerarquia e incluyen
TComObjectFactory,TTypedComObjectFactory,TAutoObject-
Factory y TActiveXControlFactory.Las fabricas de clases son impor-
tantes y todo servidor COM las necesita. Normalmente, 10s programas de Delphi
utilizan fabricas de clases creando un objeto en la seccion de inicializacion de la
unidad que define la clase del objeto de servidor correspondiente

Un primer servidor COM


No hay mejor manera de entender COM que construir un simple servidor COM
hospedado por un DLL. Una biblioteca que alberga un objeto COM se indica en
Delphi como una biblioteca ActiveX. Por esta razon, podemos empezar el desa-
rrollo de este proyecto seleccionando File>New>Other, pasando a la pagina de
ActiveX, y seleccionando la opcion ActiveX Library. De este mod0 generamos
un archivo de proyecto de ejemplo llamado FirstCom.Este es el codigo fuente
completo:
library FirstCom;

uses
ComServ;
exports
DllGetClassObject,
DllCanUnloadNow,
DllRegisterServer,
DllUnregisterServer;

begin
end.

Las cuatro funciones que exporta la DLL son necesarias para la compatibili-
dad COM y el sistema las usa de la manera siguiente:
Para acceder a la biblioteca de clases (Dl 1 G e t C l a s sob j e c t ) .
Para comprobar si el servidor ha destruido todos sus objetos y se puede
descargar de la memoria (Dl ICanUnloadNow).
Para aiiadir o eliminar informacion sobre el servidor en el Registro de
Windows ( D l l R e g i s t e r S e r v e r y D l l U n r e g i s t e r S e r v e r ) .
Normalmente, no tendremos que implementar estas funciones, porque Delphi
nos ofrece una implementacion predefinida en la unidad c o m s e r v . Por esta ra-
zon, en el codigo de nuestro servidor, solo necesitamos exportarlas.

Interfaces y objetos COM


Ahora que tenemos la estructura de nuestro servidor COM, podemos empezar
a desarrollarla. El primer paso es escribir el codigo de la interfaz que queremos
implementar en el servidor. Este es el codigo de una interfaz sencilla, que podria-
mos aiiadir a una unidad independiente (llamada NumIntf en el ejemplo):
type
INumber = interface
['{B4131140-7C2F-llD0-98DO-444553540000)']
function Getvalue: Integer; stdcall;
procedure SetValue (New: Integer) ; stdcall;
procedure Increase; stdcall;
end;

Despues de declarar la interfaz personalizada, podemos agregar el objeto real


a1 servidor. Para ello, podemos emplear el COM Object Wizard (disponible en
la ficha ActiveX del cuadro de dialog0 File>New>Other). Podemos ver este
asistente en la figura 12.2. En el hay que escribir el nombre de la clase del servi-
dor y una descripcion. Hemos desactivado la generacion de la biblioteca de tipos
(en cuyo caso el asistente desactiva el campo de interfaz en Delphi 7, no como
sucedia en Delphi 6) para evitar presentar demasiados temas a la vez. Tambien
hay que elegir un modelo de instancia y de threahng.
I1,d 1
I 1 -.i
, , , 1 8 1 , ~ -'
,,.: I
1 Desc~ifliar:
-
Servidor COM: La Biblia de Delphi 7

I
Figura 12.2. El asistente COM Object Wizard.

El codigo generado por el asistente COM Object Wizard es muy sencillo. La


interfaz contiene la definicion de la clase que hay que rellenar con metodos y
datos:
type
TNumber = c l a s s (TComObject, INumber)
protected
[ D e c l a r e I N u n ~ e rm e t h o d s h e r e )
end ;

Ademas del GUID para el servidor (almacenado en la constante C l a s s


N u m b e r ) , tambiln hay c6digo en la secci6n i n i t i a l i z a t i o n de la unidad,
que usa la mayoria de las opciones especificadas en el cuadro de dialog0 del
asistente:
initialization
TComObjectFactory.Create(ComServer, TNumber, Class-Number,
' Number ' ,
' N u m b e r S e r v e r ' , ciMultiInstance, tmApartment) ;

Este codigo crea un objeto de la clase T C o m O b je c t F a c t o r y, pasando como


parametros el objeto global C o r n s e r v e r , una referencia de clase para la clase
que acabamos de definir, el GUID para la clase, el nombre del servidor, la des-
cripcion del servidor, y 10s modelos de instanciacion e hilos (threading) que que-
remos usar.
El objeto global C o m s e r v e r , definido en la unidad C o m s e r v , es un gestor
de las fabricas de clases disponibles en la biblioteca del servidor. ~ s t usa e su
propio metodo F o r E a c h F a c t o r y para buscar la clase que soporta una solici-
tud dada de un objeto COM, y guarda la pista del ni~merode objetos encontrados.
Como ya hemos visto, de hecho, la unidad C o m S e r v implementa las funciones
requeridas por la DLL para ser una biblioteca COM.
Despues de esaminar el codigo fuente generado por el asistente, podemos com-
pletarlo aiiadiendole a la clase TNumber 10s metodos necesarios para implementar
la interfaz I N u m b e r y escribiendo su codigo, y tendremos un objcto COM fun-
cional en nuestro servidor

Modelos de instancias e hilos COM


Cuando se crea un servidor COM, deberia escogerse un modelo de
instanciacion e hilos apropiado, que puede afectar de manera significativa
el comportamiento del servidor COM.
La instanciacion afecta principalmente a 10s sewidores fuera de proceso
(cualquier sewidor COM que se encuentre en un archivo ejecutable inde-

Multiple:lndica que cuando varias aplicaciones cliente necesitan el


objeto-COM,el sisiema debe arrancar multiples instancias del servidor.

. I
el oojeto. .
fJnica: Indica que, incluso cuando varias aplicaciones cliente necesitan
. LVM, SOIO.a
existe I . . . ae la ap~icac~on
. una unica insrancia
. I .. - , servi-
.

dor; crea multiples objetos internos parar servir Ias peticiones.


Interna: lndica aue el obieto solo ~ u e d ecrearse dentro del servidor; las
aplicaciones cliente no pueden solicitar este tip0 de objeto (esta conf
guracion especifica afecta tambien a 10s servidores en proceso).
La segunaa aeclsion riene que ver con el sopone ae nuos oel oojeto LVIVI,
que solo es valido para 10s servidores en proceso (DLL). El modelo de hilos
(o threading) es una decision conjunta de las aplicaciones cliente y servi-
dor: si arnbas partes acuerdan usar un modelo, este se usa para la conexion.
Si no se llega a un acuerdo, COM aun puede establecer una conexion me-
diante intermediation (marshaling),-
que
-
puede
.
ralentizar las operaciones.
Tambien hay que tener presente que un senidor no solo debe publicar su
modelo de hilos en el Registro (como resultado de establecer la opcion en el
,,

codigo. Estos son 10s puntos clave de 10s diversos modelos de hilos:
Modelo unico: No se trata de un soporte real para hilos. Las solicitudes
I1 1. LUIW
que llegan a1 servlaor1 n .-I' I I.
se serializan para que el clienre pueaa
- - L I

realizar una operacion cada vez.


Modelo apartamento, o " apartamento monohilo": Solo el hilo que
creo el objeto puede llamar a sus metodos. Esto significa que las peti-
ciones para cada objeto de sewidor se serializan, per0 que otros objetos
del mismo sewidor pueden recibir peticiones a1 mismo tiempo. Por este
motivo, el objeto de servidor debe tomar precauciones adicionales a1
acceder a datos globales del servidor (rnediante secciones criticas, mutex
u otras tdcnicas-de sincronizaci6n). ~ s t mode10
e de hilos se suele usar
n a r a r n n t r n l p c A r t i v ~ Xi n r l ~ ~ i r e
ln ~
1 n t ~ r n t - tF v n l n r ~ r

Modelo libre, o "apartamento multihilo": El cliente no tiene restric-


ciones, lo que significa que mfiltiples hilos pueden usar el mismo objeto
-a1- -minmn -- ----=-. Pnr
-------- tiemnn ---- mntivn
- -- ecte --- -"mhndn
----.-,cnda
" -- cada nhictn
----- Ae --J--- ----
dche ---I

protegerse a si mismo y a 10s datos no locales que utiliza contra ~ a r i a s


llamadas simultheas. Este modelo de hilos es mas complejo de sopor-
tar para un servidor que 10s modelos unico y de apartamento, ya que
inchso el acceso a 10s datos de la propia instancia del objeto deben
llevarse a cab0 con atenci6n sobre la seguridad de hilos.
Ambos: Este objeto de sewidor soporta el modelo libre y el modelo
apartamento.
-
Neutral: Introducido en Windows 2000 v- disponible s61o baio COM+,
este modelo indica que multiples clientes pueden llamar a1 objeto en
diferentes hilos a1 mismo tiempo, pero COM garantiza que el mismo
metodo no se invocara dos veces a la vez. Es necesario tornar precau-
,. . . . -. I . . . ..
clones rrente a accesos concurrentes a 10saatos ael oojeto. rrajo LVM,
..-..
se proyecto sobre el modelo apartamento.

Inicializacion del objeto COM


Si volvemos a la definition de la clase TComObj e c t , observaremos que tiene
un constructor no virtual. En realidad, tiene varios, cada uno de 10s cuales llama
a1 metodo virtual Initialize.Por esta razon, para configurar un objeto COM
de inanera apropiada, no deberiamos definir un nuevo constructor (que nunca
sera llamado), sino, en su lugar, sobrescribir su mCtodo Initialize,como
hemos hecho en la clase TNumber . Esta es la version final de esta clase:
type
TNumber = class (TComObject, INumber)
private
fValue: Integer;
public
function Getvalue: Integer; virtual; stdcall;
procedure SetValue (New: Integer); virtual; stdcall;
procedure Increase; virtual; stdcall;
procedure ~nitialize; override;
destructor Destroy; override;
end ;

Como se puede ver, hemos sobrescrito tambien el destructor de la clase, por-


que queremos comprobar la destruccion automatica de 10s objetos COM provis-
tos por Delphi.
Prueba del servidor COM
Ahora que hemos terminado de escribir nuestro objeto servidor COM, pode-
mos registrarlo y usarlo. Para registrarlo simplemente hay que compilar el codigo
y emplear la orden RuwRegister ActiveX Server del menu de Delphi. Esto
sirve para registrar el servidor en nuestro equipo, actualizando el Registro local.
A1 distribuir este servidor, habra que instalarlo en 10s ordenadores de 10s clien-
tes. Para esto, podemos escribir un archivo REG para instalar el servidor en el
Registro. Sin embargo, no es realmente la mejor tecnica porque el servidor ya
incluye una funcion que podemos activar para registrarlo. Esta funcion se puede
activar mediante el entorno Delphi, como se ha visto, o de otras maneras:
Se puede pasar la DLL del servidor COM como un parametro de linea de
comandos a1 programa RegSvr32. exe de Microsoft, que se encuentra
en el directorio \Windows\System.
Se puede usar el programa similar TRegSvr . exe de demostracion que
viene con Delphi. (La version compilada esta en el directorio \Bin, y su
codigo fuente esta en el directorio \Demos\ActiveX.)
Se puede dejar que el programa de instalacion llame a la funcion de regis-
tro del servidor.
Una vez registrado el servidor, podemos volver a la parte cliente de nuestro
ejemplo. Esta vez, el ejemplo se 1lamaTestCom y esta guardado en un directorio
aparte. El programa carga la DLL por medio del mecanismo COM, gracias a la
informacion del servidor presente en el Registro, por lo que no es necesario que el
cliente conozca el directorio en el que reside el servidor.
El formulario que presenta este programa es muy similar a1 que hemos usado
para probar algunas de las DLL en capitulos anteriores. En el programa cliente,
debemos incluir el archivo de codigo fuente con la interfaz y volver a declarar el
nuevo GUID del servidor COM. El programa arranca con todos 10s botones
desactivados (en tiempo de diseiio), y 10s activa solo despues de que se haya
creado un objeto. De esta forma, si ocurre una excepcion mientras se crea uno de
10s objetos, 10s botones relacionados con el objeto no se activaran:
procedure TForml.FormCreate(Sender: TObject);
begin
// c r e a e l p r i m e r o b j e t o
Numl : = CreateComObject (Class-Number) as INumber;
Numl. SetValue (SpinEdit1.Value);
Label1 .Caption : = ' N u m l : ' + IntToStr (Numl.Getvalue) ;
Buttonl.Enab1ed : = True;
Button2.Enabled : = True;

/ / crea e l segundo o b j e t o
Num2 : = CreateComObject (Class-Number) as INumber;
Label2. Caption : = ' Num2: ' + IntToStr (Num2.Getvalue) ;
Button3.Enabled : = True;
Button4.Enabled : = True;
end ;

Se debe tener en cuenta particularmente la llamada a C r e a t eComOb j e c t y


la siguiente conversion a s . La llamada a la API inicia el mecanismo de construc-
cion del objeto COM que ya hemos descrito. Esta llamada tambien carga
dinamicamente el servidor DLL. El valor de retorno es un objeto IUnknown.
Este objeto debe convertirse a1 tip0 de interfaz adecuado antes de asignarlo a 10s
campos Numl y Num2; que ahora tienen el tipo de interfaz INumber como su
tip0 de dato.

ADVERTENCIA: Para convertir de nuevo una interfaz a su tip0 real,


siempre se debe utilizar la conversion as, que para interfaces realiza una
llamada a Querylnterface de f o m a interna. Alternativamente, se puede
---I:--- ..-aI I - - - L A:---.- - em
- nm.A-.~..~n~--n
, . . m ~ ~ eC- -1 ---- L
interfaces, la conversion as (o una llamada a una funcibn especifica) es la
..
a orro punrera a e inrerraz C . .-
"nica f o m a de extraer una interfaz de otra. Convertir un punt&o de interfaz
airecramenre es un gran error.

El programa tambien tiene un boton (hacia la parte inferior del formulario)


con un controlador de eventos que crea un nuevo objeto COM usado para obtener
el valor del numero siguiente a1 100. Para ver por que se ha aiiadido este metodo
a1 ejemplo, es necesario hacer clic sobre el boton del mensaje que muestra el
resultado. Entonces se observa un segundo mensaje que indica que el objeto ha
sido destruido. Esto demuestra que simplemente al permitir que una variable de la
interfaz salga del ambito. automaticamente llama a1 metodo Release del obje-
to, disminuye el recuento de referencias a1 objeto y lo destriye si su recuento de
referencias llega a cero.
Lo mismo ocurre con 10s otros dos objetos en cuanto termina el programa.
Aunque el programa no destruya esplicitamente 10s dos objetos, ambos se veran
destruidos, como muestra claramente el mensa-je de su destructor D e s t r o y . Esto
ocurre porque fueron declarados para ser de tipo interfaz, y Delphi va a utilizar
para ellos el recuento de referencias. Por cierto, en caso de que se desce destruir
una referencia a un objeto COM con una interfaz, no se puede llamar a un metodo
F r e e (las interfaces no disponen de F r e e ) sino que se puede asignar nil a la
variable de la interfaz; esto provocara la eliminacion de la referencia y posible-
mente la destruccion del objeto.

Uso de las propiedades de la interfaz


Podemos ampliar el ejemplo aiiadiendole una propiedad a la interfaz INumber.
Cuando se le aiiade una propiedad a una interfaz, hay que indicar el tip0 de dato
y despues las directivas read y write. Se pueden tener propiedades de solo
lectura o de solo escritura, per0 las clausulas read y write deben referirse
siempre a un metodo, porque las interfaces solo contienen metodos.
Aqui esta la interfaz actualizada, que forma parte del ejemplo PropCom:
type
INumberProp = i n t e r f a c e
['{B36C5800-8E59-11D0-98D0-444553540000}']
f u n c t i o n GetValue: Integer; stdcall;
p r o c e d u r e SetValue (New: Integer); stdcall;
property Value: Integer r e a d GetValue write SetValue;
p r o c e d u r e Increase; stdcall;
end ;

Se le ha dado un nuevo nombre a esta interfaz y, lo que es mas importante, un


nuevo identificador de interfaz. Se podria haber heredado el nuevo tip0 de interfaz
del anterior, per0 esto no ofreceria ninguna ventaja real. COM no soporta heren-
cia y, desde la perspectiva de COM, todas las interfaces son diferentes simple-
mente porque tienen distintos identificadores de interfaz. No es necesario decir
que en Delphi se puede usar la herencia para mejorar la estructura del codigo de
las interfaces y de 10s objetos de servidor que las implementan.
En el ejemplo PropCom, se ha actualizado la declaracion de la clase del servi-
dor simplemente haciendo referencia a la nueva interfaz y proporcionando un
nuevo identificador del objeto de servidor. El programa cliente (llamado Testprop)
puede simplemente utilizar ahora la propiedad value en lugar de 10s metodos
SetValue y GetValue. Aqui vemos un pequeiio fragment0 del metodo
Formcreate:
Numl : = CreateComObject (Class-NumPropServer) as INumberProp;
Numl.Value : = SpinEditl.Value;
Labell .Caption : = ' N u m Z : ' + IntToStr (Numl.Value) ;

La diferencia entre utilizar metodos o propiedades para una interfaz es solo


sintactica, porque las propiedades de la interfaz no pueden acceder a datos priva-
dos como hacen las propiedades de clase. A1 usar propiedades se puede hacer el
codigo un poco mas legible.

Llamada a metodos virtuales


Hemos construido un par de ejemplos basados en COM, per0 puede que a h no
se sienta comodo con la idea de un programa que llama a 10s metodos de objetos
creados dentro de una DLL. ~ C o m oes posible si esos metodos no 10s ha exporta-
do la DLL? El servidor COM (la DLL) crea un objeto y lo devuelve a la aplica-
cion que ha realizado la llamada. Con ello, la DLL crea un objeto con una tabla
de metodos virtuales (VMT). Para ser mas precisos, el objeto tiene una VMT
para su clase mas tablas de metodos virtuales para cada una de las interfaces que
implementa.
El programa principal recibe de vuelta una variable de interfaz con la VMT de
la interfaz solicitada. Esta VMT puede usarse para invocar metodos, pcro tam-
bien para realizar peticiones sobre otras interfaces soportadas por el objeto COM
(ya que el metodo Q u e ry I n t e r f a c e esta disponible como parte de la interfaz
I U n k n o w n de la VMT).
El programa principal no necesita conocer las direcciones de mernoria de estos
metodos, porque 10s objetos la saben, del mismo mod0 que si hiciesen una llama-
da polimorfica. Pero COM es incluso mas potente y no hay que saber que lengua-
je de programacion se us6 para crear el objeto, siempre que la tabla de metodos
virtuales siga las normas deterrninadas por CON.

TRUCO:La tabla de m h d o s virtuales (VMT)compatible con COM con-


lleva un efecto inesperado. Los nombres de 10s mitodos no son importantes,
siempre que su direccion estk en la posicion apropiada en la VMT. Este es
el motivo por el que se puede proyectar un mitodo de una interfaz sobre una
funcion real que la implemente.

Para concretar, COM proporciona un estandar binario independiente del len-


guaje para 10s objetos. Los objetos que se cornparten entre 10s modulos se encuen-
tran compilados y su VMT tiene una estructura particular determinada por COM
y no por el entorno de desarrollo que se haya utilizado.

Automatizacion
Hasta ahora hemos visto que se puede utilizar COM para permitir que un
archivo ejecutable y una biblioteca compartan objetos. Sin embargo, la mayoria
de las veces, 10s usuarios quieren aplicaciones que se comuniquen entre si. Uno de
10s enfoques que se pueden utilizar para obtener este objetivo es la Automatizacion
(Azrtomation, antes llamada Automatizacion OLE u OLE Azrtomatlon). A conti-
nuacion comentaremos el desarrollo de controladores Autornatizacion para Word
y Excel, mostrando como transferir inforrnacion de bases de datos a estas aplica-
ciones.
-- .- ---
NOTA: La documentacibn actual de Microsoft usa el termino Automati-
zacibn en lugar de Automatizacion OLE y usa 10s timinos documento
activo y documento compuesto en lugar de Documento OLE. Este libro
utiliza la nueva tenninologia, aunque la antigua tenninologia "OLE" sigue
estando indicada y probablemente resulte mhs clara.

En Windows, las aplicaciones no viven en mundos aparte, sino que 10s usua-
rios suelen querer que estas interactuen entre si, a1 igual que 10s usuarios pueden
copiar y pegar datos entre aplicaciones. Sin embargo, cada vez hay mas progra-
mas que ofrecen una interfaz de Automatizacion para que otros programas la
dirijan.
Mas alla de la gran ventaja de la automatizacion programada, en comparacion
con operaciones manuales del usuario, estas interfaces son completamente neu-
trales en cuanto a1 lenguaje, por lo que se puede usar Delphi, C++, Visual Basic
o un lenguaje de macros para controlar un servidor de Automatizacion, indepen-
dientemente del lenguaje de programacion usado para escribirlo. La Automatizacion
tiene una implernentacion sencilla en Delphi gracias a la labor extendida del
compilador y la VCL para proteger a 10s desarrolladores de su complejidad. Para
soportar Automatizacion, Delphi proporciona un asistente y un potente editor de
bibliotecas de tipos y soporta interfaces dobles. Cuando se usa una DLL en pro-
ceso, la aplicacion cliente puede usar el servidor y llamar directamente a sus
metodos, porque estan en el mismo espacio de direccion. Cuando se usa
Automatizacion, la situacion es mas compleja. El cliente (llamado controlador) y
el servidor, son dos aplicaciones separadas que funcionan en distintos espacios de
direccion. Por esta razon, el sistema debe enviar las llamadas a metodos usando
un complejo mecanismo de paso de parametros llamado marshalling
(intermediacion).
Tecnicamente, soportar Automatizacion en COM implica implementar la
interfaz IDispatch, declarada en Delphi dentro de la unidad System como:
type
IDispatch = interface (IUnknown)
['{00020400-0000-0000-COOO-000000000046)"
function GetTypeInfoCount(out Count: Integer): HResult;
s tdcall ;
function GetTypeInfo(Index, LocaleID: Integer;
o u t TypeInfo) : HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
Namecount, LocaleID: Integer; DispIDs: Pointer):
HResult; stdcall;
function Invoke (DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params;
VarResult, ExceptInfo, ArgError: Pointer): HResult;
s tdcall ;
end;

El primer0 de 10s dos metodos devuelve la informacion de tipo; 10s dos ultimos
pueden usarse para invocar un metodo real del servidor de Automatizacion. En
realidad, la invocacion solo la realiza el ultimo metodo, I n v o k e , mientras que
G e t I D s O f N a m e s se usa para determinar el identificador de invocacion (nece-
sario para I n v o k e ) a partir del nombre del metodo. Cuando se crea un servidor
de Automatizacion en Delphi, todo lo que se tiene que hacer es definir una biblio-
teca de tipos e implementar su interfaz. Delphi proporciona el resto de lo necesa-
rio mediante su compilador y el codigo de la VCL (en realidad una parte de la
VCL llamada originalmente marco de trabajo DAX).
El papel de IDispath resulta mas patente cuando se considera que hay tres
maneras de que un controlador llame a 10s metodos expuestos por un servidor de
Automatizacion:
Puede solicitar la ejecucion un metodo, pasando su nombre en una cadena,
de forma similar a la llamada dinamica a una DLL. Esto es lo que hace
Delphi cuando s e usa una variante para llamar a1 servidor de
Automatizacion. Esta tecnica es muy sencilla de usar, per0 es bastante
lenta y no ofrece una gran verificacion de tipos del compilador. Implica
una llamada a G e t I D s O f Names seguida de otra a I n v o k e .
Puede importar la definition de una interfaz de Delphi de invocacion
( d i s p i n t e r f a c e ) para el objeto en el servidor y llamar a sus mCtodos
de forma mas directa (simplemente enviando un numero, es decir, llaman-
do directamente a I n v o k e ya quc el DispID de cada metodo se conoce
en tiempo de compilacion). Esta tkcnica, basada en interfaces, permite a1
compilador comprobar 10s tipos de 10s parametros y produce un codigo
mas rapido, per0 requiere un poco mas de esfuerzo por parte del programa-
dor (el uso de una biblioteca de tipos). Ademas. terminamos vinculando el
controlador de la aplicacion a una version especifica del servidor.
Puede llamar directamente a la interfaz, mediante la tabla virtual de la
interfaz, tratandola por ejemplo como un objeto COM normal. Esto fun-
ciona en la mayoria de 10s casos ya que la mayor parte de las interfaces de
10s servidores de Automatizacion ofrecen interfaces duales (que soportan
tanto IDispatc h como una interfaz COM simple).
_
-.-
N O T---.
.. .--.-
--
A r Sa niteAe
- .- _
r..--- iicnr lrna a
-
"
.. --
....-vnrinnte
. .----- --- -- ..--
..
nnrn oi~nrAnr
--a a - - -- -
-

--. a rrn-- n hJ-"-


rinn referencia
-A a ietn .,.
de Autornatizacion. En el lenguaje Delphi, una variante es un tipo de datos
de tipo variable, es decir, una variable que puede tomar distintos tipos de
datos como valor. Los tipos de datos variantes incluyen 10s basicos (como
valores
-. - - - enteros.
- - - .- - - cadenas- caracteres v
-- - ) ,booleanosl. oero
- - -----.--,, - - - tambikn el
- - tioo de
--r - - -

interfaz I D i s p a t c h . Se comprueba el tipo de las variantes en tiempo de


ejecucion; por lo que el compilador puede compilar el c6digo incluso aun-
que no conozca 10s metodos del sewidor de Automatizacion.

Envio de una llamada Automatizacion


La mayor diferencia entre 10s dos metodos es que el segundo normalmente
requiere una biblioteca de fipos, una de las bases de COM. Una biblioteca de
tipos es basicamente un conjunto de informacion sobre tipos que se suele encon-
trar tambien en un objeto COM (sin soporte de envio). Este conjunto suele descri-
bir todos 10s elementos (objetos, interfaces e informacion sobre otros tipos) que
estan disponibles en un servidor COM generic0 o en un servidor de Automatizacion,
La diferencia clave entre una biblioteca de tipos y otras descripciones de estos
clementos (como codigo C o Pascal) es que una biblioteca de tipos es indepen-
diente del lenguaje. Los elementos de tipos son definidos por COM como un
subconjunto de 10s elementos estandar de lenguajes de programacion y cualquier
herramienta de desarrollo puede usarlos.
Esta informacion es necesaria, porque si se invoca un metodo de un objeto de
Automatizacion mediante una variante, el compilador de Delphi no necesita cono-
cer nada acerca de este metodo en tiempo de compilation. Un pequeiio fragment0
de codigo quc usa la antigua interfaz de Automatizacion de Word, registrada
como Word. Basic, muestra lo simple que resulta para un programador:
var
VarW: Variant;
begin
V a r W : = Createoleobject ( 'Word.Basic') ;
VarW. FileNew;
V a r W . Insert ( ' L a b i b l i a d e Delphi 7 ' ) ;

- *
NOTA: Como s e vera mas adelante, las ultimas versiones de Word siguen
registrando la interfaz Word. Basic, que se corresponde con el lenguaje
interno de macros WordBasic, pero tambikn registran la nueva interfaz
Word .Application,que se corresponde con el Ienguaje de macros VBA.
Delphi ofrece componentes que simplifican la conexibn con las aplicacio-
nes de Microsoft Offke.

Estas tres lineas de codigo arrancan Word (a no ser que ya se este ejecutando),
crean un nucvo documento y aiiaden algo de texto. Puede verse el resultado de
esta aplicacion en la figura 12.3. Lamentablemente, el compilador de Delphi no
tiene ningun mod0 de comprobar si existen 10s metodos. Realizar toda la compro-
bacion de tipos en tiempo de ejecucion es algo arriesgado, ya que si se comete el
mas minimo error ortografico en el nombre de una funcion no se vera ningtin
aviso sobre el error hasta que se ejecute el programa y se llegue a esa linea de
codigo. Por ejemplo, si se hubiera escrito VarW. Isnert,el compilador no se
quejaria del error, pero en tiernpo de ejecucion ese error saltaria, ya que no reco-
noce el nombre, Word asume que el metodo no existe.
Aunque la interfaz IDispatch soporta el enfoque que se acaba de ver, tambien
es posible (y mas seguro) que un servidor exporte la descripcion de sus interfaces
y objetos mediante una biblioteca de tipos. Esta biblioteca de tipos puede enton-
ces convertirse mediante una herramienta especifica (como Delphi) en definicio-
nes escritas en el lenguaje que se quiere usar para escribir el programa cliente o
controlador (como el lenguaje Delphi). Esto posibilita que un cornpilador corn-
pruebe que el codigo es correct0 y poder usar las caracteristicas Code Completion
y Code Parameters en el editor de Delphi.
Figura 12.3. El documento d e Word creado mediante la aplicacion WordTest d e
Delphi.

Una vez que el compilador ha realizado sus pruebas, puede usar una de las dos
tecnicas distintas para enviar la pcticion a1 servidor. Puede usar una simple
V T a b l e (es decir, una entrada en una declaracion de tipo de interfaz) o usar una
d i s - p i n t e r f a c e (una interfaz de envio o dispatch).
Ya se ha utilizado una declaracion de tip0 de interfaz, asi que deberia resultar
familiar. Una d i s p i n t e r f ace es basicamente un mod0 de proyectar cada
entrada de un interfaz sobre un numero.
Las llamadas a1 servidor pueden enviarse entonces mediante llamadas de nu-
meros a I D i s p a t c h .I n v o k e , sin el paso adicional de llamar a I D i s p a t c h .
G e t I D s O f N a m e s . Se puede considerar que esta es una tecnica intermedia, a
medio camino entre enviar el nombre de la funcion y usar una llamada directa de
la V T a b l e

1 terf ace w penerado automaticummts@c#-el editor de la bibliotecn'do

wua c;lcrnc;nio, r e a o o n l y y wr J. L-eonl.y sou cspr--c~r~cauores


aalciona-
les para propiedades.
El termino usado para describir esta capacidad de conectarse a un servidor de
dos maneras distintas, usando un enfoque mas dinamico o mas estatico, es interfaz
dual.
Cuando se escribe un controlador COM, se puede escoger acceder a 10s meto-
dos de un servidor de dos maneras: se puede usar el enlace tardio y el mecanismo
proporcionado por d i s p i n t e r f a c e o se puede usar el enlace temprano y el
mecanismo basado en las VTables, 10s tipos de interfaz.
Es importante tener presente que (ademas de otras cosas) distintas tecnicas
tendran como resultado una ejecucion mas o menos rapida. Buscar una funcion
por su nombre (y realizar la comprobacion de tipos en tiempo de ejecucion) es el
enfoque mas lento, mientras que usar d i s p i n t e r f a c e es mucho m h rapido, y
usar la llamada directa a la VTable es el enfoque mas rapido de todos. Veremos
una demostracion de esto en el proximo ejemplo TLibCli.

Creacion de un servidor de Automatizacion


Comencemos por la creacion de un servidor de Automatizacion. Para crear un
objeto de Automatizacion. se puede usar el Automation Object Wizard de Delphi.
Comenzamos con una nueva aplicacion, abriendo el Object Repository median-
te File>New>Open, yendo a la pagina de ActiveX y seleccionando Automation
~b j e c t . Aparecera cl cuadro de dialogo siguiente:

En este asistente, escribimos el nombre de la clase (sin la T inicial, ya que se


aiiadira automaticamente a la clase de Delphi que la implemente) y hacemos clic
sobre el boton OK. Delphi abrira a continuacion el editor de la biblioteca de
tipos.

TRUCO:Delp-. pude g e n e t q seyidores dc Automatizacion que tambikn


exporten evkntos. Hay quc activar la crr'silla de verificacibn correspondien-
te cn el asistente ~ R e l p hdi a & a las entradas apropiadas en la biblioteca
dc tipos y e n d &&,go ffiemq~e gencrc:
El editor de bibliotecas de tipos
El editor de Type Library es la herramienta que debemos de utilizar para
definir una biblioteca de tipos en Delphi. La figura 12.4 muestra su ventana
despues de afiadirle algunos elementos. Este editor permite aiiadir metodos y pro-
piedades a1 objeto sewidor de Automatizacion creado o a un objeto COM que se
haya creado mediante el COM Object Wizard.Despues, se puede generar tanto el
archivo de la biblioteca de tipos (TLB) como el codigo fuente Delphi correspon-
diente, guardado en una unidad llamada unidad de importation de la biblioteca de
tipos.

Figura 12.4. El editor de la biblioteca de tipos con 10s detalles de una interfaz.

Hay dos recomendaciones que conviene seguir para trabajar mejor con el edi-
tor de biblioteca de tipos de Delphi. La primera y mas sencilla es que si se hace
clic con el boton derecho del raton sobre la barra de herramientas y se activa la
opcion Text Labels se vera en cada boton de la barra un texto comentando su
efecto, lo que simplificara el uso del editor.
La segunda y m b importante es acudir a la pagina Type Library del cuadro
de dialog0 Environment Options de Delphi y activar el boton de radio de len-
guaje Pascal en lugar del lenguaje IDL. Esta configuration determina la notacion
usada por el editor de biblioteca de tipos para mostrar 10s metodos y parametros,
e incluso para editar 10s tipos de 10s parametros de un metodo o el tip0 de una
propiedad.
A no ser que se este acostumbrado a escribir codigo COM en C o C++, proba-
blemente se prefiera pensar en tkrminos de Delphi que en terminos de IDL.
ADVERTENCIA:En esta parte del libro comentaremos cdmo interactuar
con el editor de la biblioteca de tipos cuando se dispone de esta c o d g u r a -
cion, ya que proporciona tambiCn una descripcidn en tirminos de IDL seria
tanto mas confuso como complejo, sin necesidad alguna de ello.

Para crear un ejemplo, podemos aiiadir a1 servidor una propiedad y un metodo


a1 servidor usando 10s botones correspondientes de la barra de herramientas del
editor y escribiendo sus nombres ya sea en el control TreeView que se encuentra
a la izquierda de la ventana o bien en el cuadro de edicion Name, a la derecha.
Aiiadiremos estos dos elementos a una interfaz que hemos llamado IFirstServer.
Para el procedimiento se pueden definir 10s parametros en la pagina Parameter.
Tambien se puede establecer un tip0 de retorno para una funcion en la misma
pagina. En este caso especifico, el metodo ChangeColor no tiene parametros y
su hefinkion de Delphi seria:
procedure ChangeColor; safecall;
- .

NOTA:Los m&odos contenidos en interfaces de Automatization en Delphi


suelen utilizar la convencion de llamadas s a f e ca 11.Esto envuelve en un
bloque t rylexcept a cada mitodo y proporciona un valor de retorno
^ : - ^ - I - :--I:--
~ I C U C L C I I I I I U ~ U quc
U
^--^-
CIIUI
ILIUIC;~
^
u ---:A^ 'P--L:*-
GXILU. ~ U I I U I C plcpala
--^-^-a
~
---^L:^c^ -1-
un uujcru us;
error ampliado de COM que contendra el mensaje de la excepcion, de ma-
nera que 10s clientes interesados (como 10s clientes de Delphi) puedan re-
crear la excepcion del servidor en el lado del cliente.

Ahora podemos aiiadir una propiedad a la interfaz, haciendo clic en el boton


Property de la barra del editor. De nuevo podemos escribir un nombre, como
Value, y seleccionar a continuacion un tip0 de datos en el cuadro combinado
Type. Ademas de seleccionar uno de 10s muchos tipos ya presentes en la lista,
tambien podemos escribir directamente otros tipos, especialmente interfaces de
otros objetos. La definicion de la propiedad Value del ejemplo se corresponde a
10s siguientes elementos de la interfaz Delphi:
function Get-Value: Integer; safecall;
procedure Set-Value(Va1ue: Integer); safecall;
property Value: Integer read Get-Value write Set-Value;

A1 hacer clic sobre el boton Refresh de la barra de herramientas del editor


Type Library se genera (o actualiza) la unidad Delphi con la interfaz.

El codigo del servidor


Ahora podemos cerrar el editor y guardar 10s cambios. Esta operacion aiiade
tres elementos al proyecto: el archivo de la biblioteca de tipos, la definicion en
Delphi correspondiente y la declaracion del objeto servidor. La biblioteca de tipos
esta conectada a1 proyecto mediante una sentencia de inclusion de recursos, aiia-
dida a1 codigo fiente del archivo del proyecto:
{$R *. TLB]
Siempre es posible volver a abrir el editor Type Library usando la orden
View>Type Library o seleccionando el archivo TBL adecuado en el cuadro de
dialog0 habitual File Open de Delphi.
Como hemos dicho anteriormente, la biblioteca de tipos se convierte tambien
en una definicion de interfaz y se aiiade a una nueva unidad Pascal. Esta unidad es
bastante grande, asi que solo hablaremos de sus elementos clave. La parte mas
importante es la nueva declaracion de interfaz:
type
IFirstServer = interface (IDispatch)
['{89855B42-8EFE-llD0-98D0-444553540000]']
procedure ChangeColor; safecall;
function Get-Value : Integer; safecall;
procedure Set-Value(Va1ue: Integer); safecall;
property Value: Integer r e a d Get-Value write Set-Value;
end :

A continuation, esta dispint erf ace, que asocia un numero con cada
elemento de la interfaz IFirs t Server:
type
IFirstServerDisp = dispinterface
['{89855B42-8EFE-11D0-98D0-444553540000]']
procedure Changecolor; dispid 1;
property Value: Integer dispid 2;
end;

La ultima parte del archivo incluye una clase creadora, que se utiliza para
crear un objeto en el servidor (y por ello se usa en la parte cliente de la aplicacion,
no el servidor):
type
CoFirstServer = class
class function Create: IFirstServer;
class function CreateRemote (const MachineName: string) :
IFirstServer;
end:

Todas las declaraciones de este archivo se pueden considerar un soporte de


implernentacion interno oculto. No es precis0 comprenderlas totalmente para es-
cribir la mayoria de las aplicaciones de Automatizacion.
Finalmente, Delphi genera un archivo que contiene la declaracion del objeto de
Automatizacion. Esta unidad se aiiade a la aplicacion y es la unica con la que
trabajaremos para terminar el programa.
Esta unidad declara la clase del objeto servidor, que debera implementar la
interfaz que acabamos de definir:
tYPe
TFirstServer = class (TAutoObject, IFirstServer)
protected
function Get-Value: Integer; safecall;
procedure ChangeColor; safecall;
procedure Set-Value(Va1ue: Integer); safecall;
end :

Delphi ya nos ofrece el esquema del codigo de 10s metodos, por lo que sola-
mente tenemos que completar las lineas intermedias. En este caso, 10s tres meto-
dos se refieren a una propiedad y dos metodos que hemos aiiadido a1 formulario.
En general, no deberiamos aiiadir un codigo relativo a la interfaz de usuario
dentro de la clase del objeto servidor. Nosotros lo hemos hecho asi porque quere-
mos cambiar la propiedad V a l u e y obtener un efecto colateral (mostrar el valor
en un cuadro de edicion). Este es el formulario en tiempo de disefio:

Registro del servidor de autornatizacion


La unidad que contiene a1 objeto servidor tiene una sentencia mas, aiiadida por
Delphi a la seccion i n i t i a l i z a t i o n :

initialization
TAutoObjectFactory.Create(ComServer, TFirstServer,
Class-Firstserver, ciMultiInstance);
end.

- - -.-.7,7 - .- .
--7 7 -- - -
NOTA: En este caso hemos selecciooadouna instanciacion y a p l e . .

Todo esto no es muy distinto de la creacion de fabricas de clases que hemos


visto anteriormente. La unidad c o r n s e r v e r se conecta a la funcion I n i t Proc
del sistema para registrar todos 10s objetos COM como parte del arranque de la
aplicacion de servidor COM. La e.jecucion de este codigo se dispara mediante la
llamada Application. I nit izali ze,que atiade Delphi de manera prede-
terminada al codigo fuente del proyecto de cualquier programa.
Tambien se puede agregar la informacion del servidor a1 Registro de Windows
ejecutando esta aplicacion en el equipo de destino (donde se desea instalar el
servidor de Automatizacion), o al e.jecutarla pasandole el parametro /regserver
en la linea de comandos. Se puede hacer seleccionando Inicio>Ejecutar. usando
el Esplorador de Windows para crear un atajo, o e.jecutando el programa en
Delphi despues de haber introducido el parametro para la linea de comandos
(mediante Run> Parameters). Otro parametro de linea de comandos, /
unreg s e rve r,se utiliza para eliminar este servidor del Registro.

Creacion de un cliente para el servidor


Ahora que hemos creado un servidor, podemos preparar un programa cliente
para probarlo. Este cliente puede conectarse a1 servidor ya sea usando variantes o
la nueva biblioteca de tipos. Este segundo metodo puede implementarse manual-
mente o usando las tecnicas de Delphi para envolver servidores de Automatizacion
en componentes. Vamos a probar todos estos enfoques.
Creamos una nueva aplicacion (Ilamada TlibCli) e importamos la biblioteca
de tipos del servidor mediante la opcion de menu Project>lmport del IDE de
Delphi. Este comando muestra el cuadro de dialogo Import Type Library. que
muestra la figura 12.5. Este dialogo muestra la lista de 10s servidores COM
registrados que tengan una biblioteca de tipos en la seccion superior. Se pueden
atiadir otros proyectos a esta lista haciendo clic sobre el boton Add y buscando
despuis el modulo de archivos correcto. La porcion inferior del cuadro de dialogo
Import Type Library muestra algunos detalles de la biblioteca seleccionada (como
la lista de 10s objetos del servidor) y sobre la unidad de importacion de biblioteca
de tipos que producira este cuadro de dialogo cuando se haga clic sobre el boton
Create Unit (o el boton Install).

ADVERTENCIA: No conviene aiiadb la biblio- & tipas a la aplica-


cion cliente, porque se trata de escribir el cilntrol#lor.de Awtdmati~aci6n,
~ . proyeqto pelphi da un sontmlador no debeda
no un s e r v i d ~;El -, inchi-la
b i b l i w a &-tipis 44sepiid&4qa$ se,c~pe&.

La unidad de importacion de la biblioteca de tipos toma en Delphi su nombre


de acuerdo con la biblioteca de tipos, afiadiendo TLB al final. En este caso, el
nombre de la unidad TlibdemoLib TLB. Ya hekos comentado que uno de 10s
elementos de esta unidad, generado p i r el editor de la biblioteca de tipos, es la
clase creation. Esta es la implernentacion de la primera de las dos funciones
definidas en la interfaz de esta clase:
c l a s s f u n c t i o n CoFirstServer.Create: IFirstServer;
begin
Result : = CreateComObject (Class-Firstserver) a s
IFirstServer;
end;

TAP13 Tetrnlnal Manage1 1 0 Type Lbra~yWers~on1 0)


Te~rnRecognt~on 2 0 rJers~on2 01
4
, TlFFLoader 1 0 Type L~braryrJerston 1 0)

names: TF~rstServer
I
Ud 6 name: I~.~rchwo
desprograma\8orIand\D~h17\Impo .. I I

Figura 12.5. El cuadro d e dialog0 Type Library Import de Delphi.

Se puede emplear para crear un objeto servidor (y posiblemente arrancar la


aplicacion de servidor) en el mismo ordenador. Como se puede ver en el codigo, la
funcion es simplemente un metodo abreviado de la llamada a createcom-
Ob j etct, que nos permite crear una instancia de un objeto COM si se conoce su
GUID. Como alternativa, es posible utilizar la funcion Creat eOleOb j ect,
que precisa como parametro el nombre registrado del servidor. Esiste otra dife-
rencia entre estas funciones de creacion: CreateComOb ject devuelve un ob-
jet0 del tipo IUnknown, mientras que CreateOleOb j ect devuelve un ob.jeto
del tipo IDispatch.
En este ejemplo, utilizaremos CoFirstServer. Create.A1 crear el ob.je-
to servidor se obtiene como valor de retorno una interfaz IFirstServer que puede
usarse directamente o almacenarse en una variable variante. Veamos un e.jemplo
del primer metodo:
var
MyServer: Variant;
begin
M y S e r v e r : = CoFirstServer.Create;
MyServer.ChangeCo1or;
Este codigo, basado en variantes, no es muy distinto del correspondiente a1
primer controlador creado en este capitulo (el que usaba Microsoft Word). Este
es el codigo alternative, que tiene el mismo efecto:
var
IMyServer: IFirstServer;
begin
IMyServer : = CoFirstServer.Create;
1MyServer.ChangeColor;

Ya hemos visto como usar la interfaz y la variante. En cuanto a la intcrfaz de


envio, se puede declarar una variable del tip0 de la interfaz de envio. que en este
caso seria:
var
DMyServer: IFirstServerDisp;

Despues puede usarse para llamar a 10s metodos como es habitual, despues de
haber asignado un objeto a la variable, convirtiendo el objeto devuelto por la
clase creadora:
DMyServer : = CoFirstServer.Create as IFirstServerDisp;

Interfaces, variantes e interfaces de envio: prueba


de la diferencia de velocidad
Una de las diferencias entre estos enfoques es la velocidad. Resulta muy
complicado evaluar el rendimiento de cada ttcnica porque implican nume-
rosos factores. Se ha aiiadido a1 ejemploT1 ibcli una simple prueba para
tener una idea. El cMigo de la prueba es un bucle que accede a la propiedad
value del servidor 100 vece;. La salida real del programs esti ;el&iona 1-
da con el tiempo, que se establece a1 llamar a la funcion de la AP'I
GetTickCount antes y despub de ejecutar el bucle. (Dos posibles alte~ r-
- - _ -: _ _ :- - - _
E ...- I - _3 - n-i-~: _-- I:-.
nauvas son w a r las propias runclones temporrues ae uelpm, que son nge-
ramente menos precisas, o usar las funciones de medida de tiempo
extrernadamente precisas de la unidad de soporte multimedia, MMSystem.)
Con estc:programzl, podemos comparar de forma aproxirnada la salida ob-
tenida a1 llamar a este metodo basado en una interfaz, la version correspon-
diente basaaa en una vanante - . . una tercera verslon
e rncluso .
.. basaua en una
interfaz de envio. ~nalizando10s tiempos del ejemplo, deberia cornprobar-
se que las interfaces son m h rapidas y las variantes miis lentas, ocupando
las interfaces de envio un lugar intermedio, aunque cerca de las interfaces.

El alcance de 10s objetos de automatizacion


Otro elemento importante a tener en cuenta es el alcance de 10s objetos de
automatizacion. Los objetos variantes y de interfaz utilizan tecnicas de recuento
de referencias, por lo que si una variable que este relacionada con un objeto
interfaz se declara localmente en un metodo, el objeto se destruira al final del
metodo y el servidor puede cerrarse (si todos 10s objetos creados por el servidor se
han destruido). Por ejemplo, escribir un metodo con este codigo produce un efecto
minimo:
procedure TClientForm.ChangeColor;
var
IMyServer: IFirstServer;
begin
IMyServer : = CoFirstServer.Create;
1MyServer.ChangeColor;
end ;

A menos que el servidor ya se encuentre activo, se crea una copia del programa
y se modifica el color, pero entonces se cierra el servidor inmediatamente porque
el objeto de tip0 interfaz sale de su alcance. El metodo alternativo que hemos
utilizado en el ejemplo TlibCli es declarar el objeto como un campo del formu-
lario y crear el objeto COM al arrancar, como en este procedimiento:
procedure TClientForm. Formcreate (Sender: TObject) ;
begin
I M y S e r v e r : = CoFirstServer.Create;
end ;

Con este codigo, cuando el programa cliente arranca, el programa servidor se


activa inmediatamente. Cuando finaliza el programa, se destruye el campo del
formulario y se cierra el servidor. Otra alternativa mas es declarar el objeto en el
formulario, pero despuds crearlo solamente cuando se use: como en estos dos
fragmentos:
// M y S e r v e r B i s : V a r i a n t ;
if varType (MyServerBis) = varEmpty then
M y S e r v e r B i s : = CoFirstServer.Create;
MyServerBis.ChangeColor;

// IMyServerBis : IPirs tServer;


if not Assigned (IMyServerBis) then
IMyServerBis : = CoFirstServer.Create;
1MyServerBis.ChangeColor;

NOTA: Se inicia una variante como el tipo var Empty cuando se crea. Si
en cambio se asignara el valor nulo a la variante, su tip0 se convertiria en
varNull. Ambos tipos representan variantes sin valor asignado, per0 se
comportan de un mod0 diferente al evaluar la expresion. El valor varNull
.. , . .. . . .. ..
slempre se propaga a una expreslon (convlrtlendola en una expreslon nula),
mientras que el valor varEmpty desaparece sin hacerse notar.
El sewidor en un componente
A1 crear un programa cliente para este servidor u otro servidor de
Automatizacion, se puede utilizar una tecnica mejor: envolver el servidor COM
en un componente Delphi. Si se obsenla la parte final del archivo TlibdemoLib
TLB se v e r i la +declaracibn de una clase T Firstserver que hereda de
TOleServer.Se trata de un componente generado cuando se importa la biblio-
teca, que cl sistema registra en el procedimiento Register de la unidad.
Si se aAade esta unidad a un paquete, el nuevo componente servidor estara
disponible en la Component Palette de Delphi (en la pagina ActiveX, de mane-
ra predefinida). La generacion del codigo de este componente esta controlada por
una casilla de verification que se encuentra en la parte inferior del cuadro de
dialog0 Import Type Library, que mostraba la figura 12.5.
Se ha creado un nuevo paquete, PackAuto, que se encuentra disponible en un
directorio con el mismo nombre. En este paquete se ha aiiadido la directiva
LIVE SERVER-AT-DESIGN TIME en la pagina Directories/Conditionals del
cuadrode diilogo Project options del paquete. Esta directiva habilita una ca-
racteristica adicional que no se obtiene por defecto: en tiempo de diseiio, el com-
ponente servidor tendra una propiedad adicional que lista como subelementos
todas las propiedades del servidor de Automatizacion:

ADVERTENCIA:La directiva LIVE-SERVER-AT-DESIGNTIME debe


utilizarse con cuidado con 10s servidor& comple~osde ~utoma%zacion (in-
cluyendo programas como Word, Excel, PowerPoint y Visio). Ciertos ser-
. . .. .. . . . ..a.
vldores deben encontrarse en un tnOd0 especm antes cre pmer utlllzar sugunas
propiedades de sus interfaces de automatizacion.Ya que esta caracteristica
es problematica en tiempo de disefio para muchos servidores, no esta acti-
vada por defecto en Delphi.

Como podemos ver en el Object Inspector, 10s componentes tienen pocas


propiedades. Autoconnect indica cuando activar el servidor COM. Cuando el
valor es True, el objeto servidor se carga en cuanto se crea el componente envol-
torio (tanto en tiempo de ejecucion como en tiempo de diseiio). Cuando la propie-
dad A u t oConne c t tiene el valor Fa 1se, el servidor de Automatizacion solo se
carga la primera vez que se llama a uno de sus metodos. Otra propiedad,
C o n n e c t K i n d , nos indica la manera de establecer la conexion con el servidor.
Siempre se puede iniciar una nueva instancia ( c k N e w I n s t a n c e ) , utilizar la
instancia en funcionamiento ( c k R u n n i n g I n s t a n c e , que muestra una viola-
cion de acceso si el servidor no esta ya funcionando) o seleccionar la instancia
actual o iniciar una nueva si no hay ninguna disponible (ckRunningOrNew).
Por ultimo, se puede solicitar un servidor remoto utilizando c kRemo t e y anesar
directamente un servidor en el codigo despues de una conexion manual con
ckAttachToInterface.

NOTA: Para conectarse a un objeto ya existente, se necesita que estt regis-


trado en la tabla de objetos en ejecucion (Running Object Table, ROT).El
ren;c+m A e h p r e ~ l ; ~ - r l-1
n e e r & A ~ r 1 1 a m - n A n Q 1- f i a n ~ ; A n am; C C ~ V -
n u e z o b a w uuvu a u u a t r u n a v u a o v a v a u v a ~ ~ r u u ~ lu~ r~
u u u~a vu v y sa LG L
u r\=

Activeobject de la API. Por supuesto, solo se puede registrar una


instancia para cada servidor COM en un momento dado.

Tipos de datos COM


El cnvio de COM no soporta todos 10s tipos de datos disponibles en Delphi.
Esto es particularmente importante para la Automatizacion, porque el cliente y el
servidor se suelen ejecutar en espacios de direccion diferentes y el sistema debe
mover 10s datos de un lado a otro (actuar de intermediario). Tambien hay que
tener en cuenta que las interfaces COM deben estar accesibles para programas
escritos en cualquier lenguaje.
Los tipos de datos COM incluyen tipos basicos como Integer, SmallInt, Byte,
Single, Double, Widestring, Variant y WordBool (pero no Boolean).
Ademas de 10s tipos de datos basicos, podemos usar 10s tipos COM para
elementos complejos como fuentes, listas de cadenas y mapas de bits, empleando
las interfaces I F o n t D i s p , I S t r i n g s e I P i c t u r e D i s p .
Exponer listas de cadenas y fuentes
El ejemplo ListServ es una demostracion practica de la forma de ofrecer dos
tipos complejos, como una lista de cadenas y una fuente, desde un servidor de
Automatizacion escrito en Delphi. Hemos elegido estos dos tipos especificos por-
que ambos tienen soporte en Delphi.
Windows ofrece la interfaz I FontDisp y esta disponible en la unidad ActiveX.
La unidad AsCtrls de Delphi amplia este soporte proporcionando metodos de
conversion como G e t o l e F o n t y S e t o l e F o n t . Delphi proporciona la interfaz
1str i n g s en la unidad StdVCL y la unidad AxCtrls proporciona las funciones
de conversion para este tipo (junto con un tercer tipo no usado aqui, T P i c t u r e ) .
ADVERTENCIA: Para ejecutar esta apLicaci6n y otras sirnilares, debe
instalarse y registrarse la biblioteca StdVCL en el ordenador cliente. En
nuestro ordenador, se registra durante la instalacibn de Delphi.

Los metodos Set y Get de las propiedades de tipos complejos copian infor-
macion de las interfaces COM a 10s datos locales y luego desde estos a1 formula-
rio y viceversa. Los dos metodos de las cadenas, por ejemplo, hacen esto llamando
a las funciones Getolestrings y Setolestrings de Delphi. La aplica-
cion cliente usada para demostrar esta caracteristica se llama ListCli. Los dos
programas son complejos; per0 en lugar de mostrar aqui todos sus detalles, es
mejor que se estudie el codigo por si mismo, ya que 10s programadores de Delphi
no suelen utilizar esta tecnica.

Uso de programas Office


Hasta ahora, hemos creado el cliente y el servidor de la conexion Automati-
zacion. Si nuestra intencion es que las dos aplicaciones creadas cooperen, esta es
sin duda una tecnica util aunque no es la unica. Hemos visto algunos metodos
alternativos para compartir datos como 10s archivos proyectados en memoria.
(Tambien se puede usar el inensaje wm CopyData,no comentado). El valor real
de la Automatization es que es un e&mdar, por lo que podemos usarlo para
integrar programas Delphi con otras aplicaciones de nuestros usuarios. Un ejem-
plo tipico es la integracion de un programa con aplicaciones de Office, tal como
Microsoft Word y Microsoft Excel o incluso aplicaciones independientes como
AutoCAD. La integracion con estas aplicaciones ofrece una doble ventaja:
Permite a 10s usuarios trabajar en un entorno conocido, por ejemplo, gene-
rando informes y memos desde una base de datos en un formato que pue-
dan manipular facilmente.
Evita implementar funcionalidades complejas partiendo de la nada, tales
como escribir nuestro propio codigo de procesamiento de textos dentro de
un programa. En lugar de reutilizar solo componentes, podemos reutilizar
aplicaciones complejas.
Hay tambien algunos inconvenientes que merece la pena mencionar:
El usuario debe tener la aplicacion con la que se plantee la integracion e
incluso es posible que se necesite una version reciente para soportar todas
las caracteristicas que se utilizan en el programa.
Hay que aprender una nueva arquitectura de programacion, a menudo poco
documentada. Es cierto que se sigue usando Delphi, pero el codigo depen-
de de 10s tipos de datos, 10s tipos proporcionados por el servidor, y en
particular, de un conjunto de clases interrelacionadas que son, con fre-
cuencia, dificiles de entender.
Podriamos encontrarnos con un programa que solo funciona con una ver-
sion especifica de la aplicacion del servidor, sobre todo si tratamos de
optimizar las llamadas usando interfaces en lugar de variantes. En concre-
to, Microsoft no intenta mantener la compatibilidad de guiones entre las
distintas versiones de Word u otras aplicaciones Office.
Delphi simplifica el uso de las aplicaciones Microsoft Office instalando de
antemano algunos componentes listos para usar que envuelven la interfaz de
Automatizacion de estos servidores. Estos componentes, disponibles en la ficha
Servers de la paleta, se han instalado usando la misma tecnica que mostramos en
el ultimo apartado. La ventaja real tiene que ver con la tecnica de crear compo-
nentes que recubran a 10s servidores de Automatizacion existentes, en lugar de la
disponibilidad de unos componentes servidores predefinidos. Hay que tener tam-
bien en cuenta que 10s componentes de Office tienen distintas versiones segun la
version del paquete de Microsoft instalado: todos 10s componentes se instalaran,
per0 solo se registra un conjunto en tiempo de diseiio, de acuerdo con la eleccion
realizada en el programa de instalacion de Delphi. Se puede modificar esta confi-
guracion mas tarde, eliminando el paquete del componente relacionado y aiiadien-
do uno nuevo.
No vamos a ver ningun ejemplo real en esta seccion porque es dificil escribir
un programa que funcione con todas las distintas versiones de Microsoft Office.

Uso de documentos compuestos


Documentos compuestos es el nombre que da Microsoft a la tecnologia que
permite editar un documento que se encuentra dentro de otro. (Por ejemplo una
foto dentro de un documento de Word). Esta es la tecnologia que origin6 el termi-
no OLE, per0 su papel es ahora, definitivamente, mas limitado de lo que Microsoft
habia previsto cuando se introdujo a principios de 10s aiios 90. Los documentos
compuestos tienen dos capacidades diferentes, que son enlace e insercion de obje-
tos (OLE. Object Linhng and Embedding).
Insertar un objeto en un documento compuesto corresponde a una version
elegante de las operaciones de copiar y pegar realizadas con el portapapeles.
La diferencia clave reside en que a1 copiar un objeto OLE de una aplica-
cion servidor y pegarla en una aplicacion contenedor, se copian tanto 10s
datos como cierta informacion sobre el servidor (su GUID), lo que permite
activar la aplicacion servidor desde el contenedor para editar 10s datos.
Enlazar un objeto a un documento compuesto, en cambio, copia solamente
una referencia a 10s datos y a la informacion sobre el servidor. General-
mente, se activa utilizando el portapapeles y con la operacion Pegar como
hipervinculo. A1 editar 10s datos en la aplicacion contenedor, en realidad se
modifican 10s datos originales, que se almacenan en un archivo diferente.
Ya que el programa servidor hace referencia a un archivo cornpleto (solo parte
del cual puede estar enlazado en el documento cliente), el servidor se activara en
una ventana independiente, y actuara sobre el archivo original cornpleto, no solo
sobre 10s datos que se han copiado. Sin embargo, cuando se tiene un objeto in-
crustado o insertado, el contenedor puede soportar la edicion visual (o en el sitio),
que significa que se puede modificar el objeto en el context0 dentro de la ventana
principal del contenedor. Las ventanas de servidor y de la aplicacion contenedor,
sus menus y sus barras de herramientas se uniran automaticamente, permitiendo
que el usuario trabaje con una sola ventana sobre varios tipos de objetos distintos
(y por ello con distintos servidores OLE) sin abandonar la ventana de la aplica-
cion contenedor.
Otra diferencia clave entre la insercion y el enlace es que 10s datos de un objeto
incrustado se almacenan y gestionan desde la aplicacion contenedor. El contene-
dor guarda el objeto incrustado en sus propios archivos. Por contra, un objeto
enlazado reside fisicamente en un archivo independiente. En ambos casos, la apli-
cacion por el contrario tiene que saber como gestionar el objeto y sus datos (ni
siquiera como mostrarlos) sin la ayuda del servidor. Teniendo en cuenta la relati-
va lentitud de OLE y la cantidad de trabajo necesaria para desarrollar servidores
COM, son comprensibles las causas de que este enfoque jamas consiguiera pegar.
Los contenedores de documentos compuestos pueden soportar COM en diver-
so grado. Se puede colocar un objeto en un contenedor insertando un nuevo obje-
to, pegando uno desde el portapapeles, arrastrandolo desde otra aplicacion, etc.
Una vez que el objeto se encuentra en el contenedor, se pueden realizar operacio-
nes sobre el, mediante las acciones o verbos disponibles del servidor. Normal-
mente la accion de edicion es la accion predefinida (la que se realiza cuando se
hace doble clic sobre el objeto). Para otros objetos, como fragmentos de audio o
video, la reproduccion es la accion predefinida. Tipicamente se puede ver una
lista de las acciones soportadas por el objeto contenido si se hace clic con el boton
derecho del raton sobre el. La misma informacion t a m b i h se encuentra disponi-
ble en muchos programas mediante la opcion de menu EdibObject, que muestra
una lista de las acciones disponibles para el objeto actual.

El componente Container
Para crear una aplicacion contenedor COM en Delphi, hay que colocar un
componente Olecontainer en un formulario. A continuacion, hay que seleccio-
narlo y hacer clic con el boton derecho para activar su menu contextual, que
incluira una orden Insert Object. Al seleccionar esa orden, Delphi presenta el
cuadro de dialogo estandar Insert Object, que permite elegir entre una de las
aplicaciones servidor registradas en el ordenador.
Una vez insertado el objeto COM en el contenedor, el menu local del compo-
nente del contenedor mostrara varios elementos de menu personalizados que in-
cluyen ordenes para cambiar las propiedades del objeto COM, insertar otro,
copiarlo o eliminarlo. La lista contiene tambien 10s verbos o acciones del objeto
(como Edit, Open o Play). Despues de insertar un objeto COM en el contenedor,
el servidor correspondiente se pondra en marcha para permitir la edicion del
nuevo objeto. En cuanto se cierre la aplicacion servidor, Delphi actualizara el
objeto en el contenedor y lo mostrara en tiempo de diseiio en el formulario de la
aplicacion Delphi en desarrollo.
Si observamos la descripcion textual de un formulario que contenga un com-
ponente con un objeto dentro, se puede ver una propiedad Data,que contiene 10s
datos reales del objeto COM. Aunque el programa cliente almacene 10s datos del
objeto, no sabe como controlarlo y mostrarlo sin la ayuda del servidor apropiado
(que debe estar disponible en el ordenador en que se ejecute el programa). Esto
indica que el objeto esta incrustado.
Para soportar totalmente documentos compuestos, el programa deberia pro-
porcionar un menu y una barra de herramientas o un panel. Estos componentes
adicionales son importantes porque la edicion en el sitio supone una combinacion
de las interfaces del usuario del programa cliente y el programa servidor. Cuando
se active el objeto COM colocado, algunos de 10s menus desplegables pertene-
cientes a la barra de menu de la aplicacion servidor se incorporaran a la barra de
menu de la aplicacion contenedor.
La combinacion de menus es casi automatica en Delphi. Solamente hay que
definir 10s indices adecuados para 10s elementos de menu del contenedor, utilizan-
do la propiedad GroupIndex.Cualquier elemento de menu con un numero de
indice impar se sustituira por el elemento correspondiente del objeto OLE activo.
Mas especificamente, 10s menus desplegables File (0)y Window (4) pertenecen
a la aplicacion contenedor. Los menus desplegables Edit (A), View (3) y Help
(5) (0 10s grupos de menus desplegables con esos indices) son capturados por el
servidor COM. Se puede utilizar un sexto grupo llamado Object (2) para mostrar
otro menu desplegable mas entre 10s grupos Edit y View, cuando el objeto COM
este activo. El programa de demostracion OleCont que hemos escrito para demos-
trar estas caracteristicas permite a1 usuario crear un nuevo objeto llamando a1
metodo InsertObjectDialog de la clase Tolecontainer.
Despues de haber creado el objeto, podemos ejecutar su verbo principal utili-
zando el metodo DoVerb.El programa muestra tambien una pequeiia barra de
herramientas con algunos botones de mapas de bits. Hemos colocado algunos
componentes Tw inContro 1 en el formulario para asi permitir a1 usuario selec-
cionarlos y desactivar el Olecontainer.
Para mantener esta barra de herramientas o panel visible durante la edicion in
situ, hay que definir su propiedad Locked como True,lo que obliga a1 panel a
estar presente en la aplicacion y a que no lo sustituya una barra de herramientas
del servidor.
Para mostrar lo quc sucede a1 utilizar este metodo, hemos aiiadido a1 programa
un segundo panel con algunos botones mas. Dado que no hemos definido su pro-
picdad Locked,esta nueva barra de herramientas sera reemplazada por la del
servidor activo. Cuando la edicion in situ ponga en marcha una aplicacion servi-
dor que muestre una barra de herramientas, la del servidor reemplazara a la del
contenedor, como mucstra la parte inferior de la figura 12.6.

TRUCO:Para que todas las operaciones de ajuste de tamaiio funcionen sin


problema, deberiamos colocar el componente del contenedor OLE en un
componente de panel y alinear ambos con la zona de cliente del formulario.

Fie Edcih Ver lmagen Cclores A y d a

I
Figura 12.6. La segunda barra de herramientas del ejemplo OleCont (arriba) es
sustituida por la barra de herramientas del servidor (debajo).

Otra forma de crear un objeto COM es usar el metodo Pastespecial-


Dialog. a1 que llama el controlador del evento PasteSpeciallClick del
ejemplo. Otro cuadro de dialog0 estandar COM, envuelto en una funcion Delphi,
es el que muestra las propiedades del objeto, que se activa con el elemento Object
Properties del menu desplegable Edit llamando a1 metodo Object Proper-
tiesDialog del cornponente de OleContainer.
La illtima caracteristica del programa OleCont es el soporte para archivos.
Este es uno de 10s aiiadidos mas sencillos que se pueden realizar, porque el com-
ponente del contenedor OLE ya ofrece soporte para archivos.
Uso del objeto interno
En el programa anterior, el usuario determinaba el tip0 del objeto interno
creado por el programa. En este caso, casi no podemos interactuar con 10s objetos
internos. Supongamos, por el contrario, que queremos insertar un documento de
Word en una aplicacion de Delphi y luego modificarlo mediante codigo. Podcmos
hacerlo usando Automatizacion con el objeto insertado; tal y como muestra cl
ejemplo WordCont (el nombre responde a Word Container).
--
ADVERTENCIA: Ya que el ejemplo WordCont incluye un objeto de un
tipo especifico (un documento de Microsoft Word) no se ejecuta si esa
aplicacion no esta instalada. Cuando la version del servidor es diferente
tambien se pueden producir ciertos problemas. Para estas versiones proble-
maticas, puede que sea necesario reconstruir el prograrna siguiendo 10s
mismos pasos.

En cl formulario de este ejemplo, se ha aiiadido un componente


OleContai ner, luego se ha definido su propiedad AutoAct ivate como
aaManual (para que la unica interaction posible sea con nuestro codigo) y
aiiadido una barra de herramientas con un par de botones. El codigo es sencillo,
una vez que se sabc que el objeto insertado correspondc a un documento de Word.
Este es un ejemplo (la figura 12.7 muestra el efecto de estc codigo):
procedureTForml.Button3Click(Sender: TObject);
var
Document, Paragraph: Variant;
begin
// a c t i v a si no e s t d funcionando
i f n o t (OleContainerl.State = osRunning) then
OleContainer1.Run;
// o b t i e n e e l d o c u m e n t o
Document := OleContainer1.01eObject;
// a d a d e p d r r a f o s , o b t e n i e n d o e l u l t i m o
Document.Paragraphs.Add;
Paragraph : = Document.Paragraphs.Add;
// a d a d e t e x t o a 1 p d r r a f o , u s a n d o tamado de fuente aleatorio
Paragraph-Range. Font .Size : = 10 + Random (20);
Paragraph.Range.Text : = ' N e w t e x t ( ' +
IntToStr (Paragraph.Range. Font. Size) + ' ) '#13;
end ;

Controles ActiveX
Visual Basic de Microsoft fue el primer entorno de desarrollo de programas en
presentar la idea de ofrecer componentes software a1 gran mercado, incluso aun-
que el concept0 de componentes software reciclables sea anterior a Visual Basic
(proccde de las teorias de la programacion orientada a objetos). El primer estandar
tecnico promovido por Visual Basic fue VBX, una especificacion de 16 bits que
estaba completamente disponible en Delphi 1. A1 pasar a las plataformas de 32
bits, Microsoft sustituyo el estandar VBX con 10s mas potentes y mas abiertos
controles ActiveX.

Figura 12.7. El ejernplo WordCont muestra como usar Autornatizacion con un objeto
incrustado.
__P

NOTA: Los controles ActiveX solian llamarse controles OLE (u OCX).El


cambio de nombre refleja una nueva estrategia de ventas por parte de
Microsoft mas que una innovacion tkcnica. No es sorprendente entonces
I-- .--*--I-- 1 -*:-.-w - - >-.- -.. ---L! --..I - -- -..-
11- - -..
qut: 10s conrrom ncaveA se guaraen en arcnwos con la extension
I
L
.ocx.

Desdc un punto de vista general, un control ActiveX no es muy distinto de un


control de Windows. La diferencia principal esta en la interfaz del control, la
interaccion entre el control y el resto de la aplicacion. Los controles de Windows
tipicos usan una interfaz basada en mensajes; 10s objetos de Automatizacion y 10s
controles ActiveX usan propiedades, metodos y eventos (como 10s propios com-
ponentes de Delphi). Empleando la jerga de COM, un control ActiveX es un
"objeto de documento compuesto que es implementado como un servidor DLL en
proceso y soporta Automatizacion, edicion visual y una activacion endogena".
Algo que esta perfectamente claro. Veamos lo que significa. Los servidores COM
pueden implementarse de tres maneras:
Como aplicaciones independientes (por ejemplo, Microsoft Escel).
Como servidores fuera de proceso, es decir, archivos ejecutables que no
pueden ejecutarse por si mismo y solo pueden ser invocados por un servi-
dor (por ejemplo, Microsoft Graph y aplicaciones similares).
Como servidores en proceso, como las DLL que se cargan en el mismo
espacio de memoria que el programa que las utiliza.
Los controles ActiveX solo pueden implementarse mediante esta ultima tecni-
ca, que es tambien la mas rapida: como servidores en proceso. Aun mas, 10s
controles ActiveX son servidores de Automatizacion. Esto significa que se puede
acceder a propiedades de estos objetos y llamar a sus metodos. Se puede ver un
control ActiveX en la aplicacion que se usa e interactuar directamente con el en la
ventana de la aplicacion contenedor. Este es el significado del termino edicion
visual o activacion in situ. Un simple clic activa el control, en lugar del doble clic
usado por 10s documentos OLE, y el control se activa siempre que esta visible
(que es lo que significa el termino activacion endogena) sin tener que hacer doble
clic sobre el.
En un control ActiveX, las propiedades pueden identificar estados, per0 tam-
bien pueden activar metodos. Las propiedades pueden referirse a valores agrega-
dos, matrices, subobjetos. Las propiedades tambien pueden ser dinamicas (o de
solo lectura, por usar el mismo termino que en Delphi). Las propiedades de un
control ActiveX se dividen en dos grupos: propiedades de reserva que necesitan
implementar la mayoria de 10s controles; propiedades de ambiente que ofrecen
informacion sobre el contenedor (como las propiedades parent C o 1or y
Parent Font en Delphi); las propiedades extendidas gestionadas por el conte-
nedor, como la posicion del objeto; y las propiedades personalizadas, que pueden
ser cualquier cosa.
Los eventos y 10s metodos son exactamente eso. Los eventos tienen que ver con
un clic de raton, la pulsacion de una tecla, la activacion de un componente y otras
acciones especificas del usuario. Los metodos son funciones y procedimientos
relacionados con el control. No existe una gran diferencia entre 10s conceptos
ActiveX y Delphi de eventos y metodos.

Controles ActiveX frente a componentes Delphi


Antes de proceder con la creacion y uso de controles ActiveX en Delphi, va-
mos a revisar algunas de las diferencias entre 10s dos tipos de controles. Los
controles ActiveX estan basados en DLL: cuando se usan, se necesita distribuir
su codigo (el archivo OCX) junto con la aplicacion que 10s usa. En Delphi, el
codigo de 10s componentes puede enlazarse estaticamente con el archivo ejecuta-
ble o enlazarse dinamicamente empleando un paquete en tiempo de ejecucion, de
manera que siempre se puede decidir si desplegar un unico archivo grande o
muchos modulos mas pequeiios.
Disponer de un archivo independiente permite compartir codigo entre distintas
aplicaciones, como suelen hacer las DLL. Si dos aplicaciones utilizan el mismo
control (o el mismo paquete en tiempo de ejecucion), solo se necesita una copia de
el en el disco duro, y una unica copia en memoria. Sin embargo, el inconveniente
es que si 10s dos programas tienen que usar dos versiones distintas (o dos
compilaciones) del control ActiveX, pueden surgir algunos problemas de compa-
tibilidad. Una ventaja de tener un archivo ejecutable autocontenido es que tam-
bien ofrece menos problemas de instalacion.
La desventaja de usar componentes Delphi no es que haya menos componentes
Delphi que controles ActiveX, sino que si se compra un componente Delphi, solo
se puede usar en Delphi y Borland C++ Builder, por otra parte, si se compra un
control ActivcX. se puede usar en multiples entornos de desarrollo de muchos
fabricantes. Aim asi, si se desarrolla basicamente en Delphi y se encuentran dos
componentes similares basados en las dos tecnologias, lo mas recomendable es
adquirir el componente Delphi (se integrara mas con el entorno y sera por ello
mas facil de usar). Ademas, el componente Delphi nativo estara probablemente
mcjor documentado (desde el punto de vista de Delphi) y aprovechara Delphi y
sus caracteristicas del lenguaje que no estan disponiblcs en la interfaz general de
ActiveX, que tradicionalmente se basa en C y C++.
P1_, .- . -*"- <- .- . . -P

NOTA: En el mundo .NET, esta situacion cambiara completamente. No


solo se podra usar cualquier componente de sistema de un mod0 mas uni-
forme, sino que tambien se podran ofrecer componentes Delphi a otros
programas y herramientas de programacion .NET.

Uso de controles ActiveX en Delphi


Delphi trae algunos controles ActiveX preinstalados y es facil adquirir otros
de terceros. Veremos con un ejemplo sencillo como funcionan en general 10s con-
troles ActiveX. El proceso de instalacion en Delphi es sencillo:
1. Seleccionamos Component>lmport ActiveX Control en el menu de Delphi
para abrir el cuadro de dialog0 Import ActiveX en el que podemos ver la
lista de bibliotecas de controles ActiveX registradas en Windows.
2. Escogemos una y Delphi leera su biblioteca dc tipos, proporcionara una
lista de sus controles y sugerira un nombre de archivo para la unidad.
3. Si la information es correcta, simplemente hay que hacer clic en el boton
Create Unit para ver el codigo fuente Delphi que creado por el IDE como
envoltorio para el control ActiveX.
4. Hacemos clic sobre cl boton Install para afiadir esta nueva unidad a un
paquete Delphi y a la Component Palette.

Uso del control WebBrowser


Para construir un ejemplo, hemos utilizado un control ActiveX preinstalado
disponible en Delphi. A diferencia de 10s controles de terceros, no esta disponible
en la ficha ActiveX de la paleta, sino en la ficha Internet. El control se llama
WebBrowser y e s un envoltorio del motor de Internet Explorer de Microsoft. El
ejemplo WebDemo es un navegador Web muy simple; tiene un control ActiveX
TWebBrowser que cubre su zona de cliente, una barra de control cn la parte
superior y una barra de estado en la parte inferior. Para acceder a una pagina
Web dada, el usuario puede escribir una URL en el cuadro combinado de la barra
de herramientas. seleccionar una URL ya visitada (se guardan en el cuadro com-
binado) o bien hacer clic sobre el boton Open File y seleccionar un archivo local.
La figura 12.8 muestra un ejemplo de este programa.

Login
1
Code Central
Qual~tyCentral
The Coad Letter
-
Two new communities StarTeam and
CaliberRM
Get Published Two new communltles have been added to
BOOKS BDN. Starlearn and CallberRM These two
communltles wrll show you how to utlllze
Developer Support StarTeam's automated change and
Shop conflguratlon management capabllltles and
Chat CallberRM's requirements deflntlon and
management features to galn control of your
Downloads development process and Increase your return
on Investment In software development
Search
Logm "Dawd I" (Dawd Interslmone!, Borland's vlce
President of Developer Relat~onsand Chef
Evangel~st
Soapbox
SIPfrom the Ftrehose 1 5may Interview with Steve Teixeira by Clay Shannon
Dawd lnterslmone Interview vwth Steve Teixe~ra,co-author of the classic "Delphi X
Behtnd the Screen Develooer's Gu~de'Lalona with Xavier Pachecol Amona other -I

Figura 12.8. El programa WebDemo despues de escoger una pagina muy conocida
para 10s desarrolladores de Delphi.

La implernentacion real del codigo empleado para seleccionar un archivo Web


o un archivo HTML local, se halla en el metodo Gotopage:
procedure TForml.GotoPage(ReqUr1: string);
begin
WebBrowserl.Navigate (ReqUrl, EmptyParam, EmptyParam,
EmptyParam, EmptyParam);
end;

Empty Param es una OleVar iant predefinida que se puede utilizar siem-
pre que haya que pasar un valor predefinido como parametro de referencia. Es un
metodo abreviado muy util que se puede emplear para evitar crear una variable
o l e v a r i a n t vacia cada vez que haga falta un p a r h e t r o similar. El programa
llama a1 metodo Goto Page cuando el usuario hace clic sobre el boton Open File,
o cuando pulsa la tecla Intro mientras que se encuentra en el cuadro combinado o
cuando hace clic sobre el boton Go, como puede verse en el codigo fuente del
ejemplo. El programa controla tambien cuatro eventos pertenecientes al control
WebBrowser. Cuando termina la operacion de descarga, el programa actualiza el
texto de la barra de estado y tambien la lista desplegable del cuadro combinado:
procedure TForml.WebBrowserlDown10adComp1ete(Sender: TObject);
var
NewUrl : string;
begin
StatusBarl. Panels [0] .Text : = ' D o n e ' ;
// a d a d e URL a 1 c u a d r o c o m b i n a d o
NewUrl : = WebBrowserl.LocationURL;
if (NewUrl <> ' ' ) and (ComboURL.Items. IndexOf (NewUrl) < 0 )
then
ComboURL.Items.Add (NewUrl);
end;

Otros dos eventos utiles son O n T i t l e c h a n g e , usado para actualizar el titu-


lo de la ventana del programa con el del documento HTML y el evento
OnS t a t usTex t Change, utilizado para actualizar la segunda parte de la barra
de estado.
Este codigo basicamente duplica la informacion que muestran en la primera
parte de la barra de estado 10s dos controladores de evento anteriores:

Creacion de controles ActiveX


Ademas de utilizar 10s controles ActiveX existentes, podemos desarrollar otros
nuevos con mucha facilidad, usando una de estas dos tecnicas:
Utilizar el asistente ActiveX Control Wizard para convertir un control
VCL en un control ActiveX. Se puede partir de un componente VCL exis-
tente, que debe ser un descendiente de TwinContro 1 (y no debe tener
propiedades no adecuadas, en cuyo caso se eliminara del cuadro combina-
do del asistente) y Delphi lo envolvera en un ActiveX. Durante este paso,
Delphi aiiade una biblioteca de tipos a1 control. (Envolver con un control
ActiveX un componente de Delphi es justo lo contrario de lo que haciamos
para usar un control ActiveX en Delphi.)
Podemos crear un ActiveForm, colocar varios controles en su interior y
usar el formulario completo (sin bordes) como un control ActiveX. Esta
segunda tecnica aparecio para crear aplicaciones para Internet, per0 tam-
bien es una alternativa muy buena para construir un control ActiveX basa-
do en multiples controles Delphi o en componentes Delphi que no descien-
dan de T W i n C o n t r o l .
En cualquier caso, opcionalmente se puede preparar una ficha de propiedades
para el control y utilizarla como una especie de editor de propiedades para definir
el valor inicial de las propiedades del control en cualquier entorno de desarrollo
(una alternativa a1 Object Inspector de Delphi). Dado que la mayoria de 10s
entornos permiten edicion limitada, es mas importante escribir una ficha de pro-
piedades que un editor de componente o de propiedades para un control Delphi.

Creacion de una flecha ActiveX


Como ejemplo dcl desarrollo de un control ActiveX, hemos decidido tomar cl
componente Arrow dcsarrollado anteriormente y convertirlo en un control ActivcX.
No podcmos utilizarlo directamente porque era un control grafico (una subclase
de T G r a p h i c C o n t r o l ) . Sin embargo, convertir un control grafico en un con-
trol basado cn una ventana suele ser generalmente una operacion facil.
En estc caso cn particular, hemos cambiado el nombre dc la clase basica a
T C u s t o m C o n t r o l (y hemos cambiado el nombrc de la clase del control a
TMdWArrow para evitar conflictos). comos se pucde ver cn 10s archivos de codi-
go fuente de la carpeta XArrow. Dcspucs de instalar este componente en Delphi,
podemos comenzar a dcsarrollar un cjemplo. Para crear una nueva biblioteca
ActiveX. hay que seleccionar File>New>Other, ir a la ficha de ActiveX y esco-
gcr la opcion ActiveX Library. Delphi crcara un esquema basico de una DLL,
como hemos visto a1 comienzo de este capitulo. Hemos guardado esta biblioteca
como XArrow en el directorio del mismo nombre, como de costumbre.
Ahora cs el momento de utilizar cl asistente ActiveX Control Wizard, disponi-
ble en la ficha ActiveX del Object Repository, en el cuadro dc dialog0 New de
Delphi:

2wlmpl2 pas

En este asistente simplemente hay que seleccionar la clase VCL que nos intere-
sa, personalizar 10s nombres que aparecen en 10s cuadros de texto y hacer clic
sobre el boton OK: Delphi construira el codigo fuente completo de un control
ActiveX.
El uso de las tres casillas de verificacion de la parte inferior de la ventana del
asistente puede no resultar obvio. Si hacemos que el control sea licenciado, Delphi
incluira una clave de licencia en el codigo y proporcionara este mismo GUID en
un archivo .LIC independiente. Este archivo de licencia es necesario para usar el
control en un entorno de diseiio sin la clave de licencia apropiada para el control
o para usarlo dentro de una pagina Web. La segunda casilla de verificacion per-
mite incluir informacion sobre la version para el ActiveX en el archivo OCX. Si
esta activada la tercera casilla de verificacion, el asistente aiiadira automaticamente
a1 control un cuadro Acerca de.
Si echamos un vistazo a1 codigo que genera el asistente, veremos que el ele-
mento clave es la creacion de una biblioteca de tipos y, por supuesto, una unidad
de importacion de la biblioteca de tipos correspondiente con la definicion de una
interfaz (dispinte r face) y otros tipos y constantes. En este ejemplo, el ar-
chive de importacion se llamax~rrowT L B . PAS.Lo mas aconsejable es estu-
diarlo para comprender corn0 define elp phi un control ActiveX. La unidad incluye
un GUID para el control, constantes para la definicion de 10s valores correspon-
dientes a 10s tipos COM enumerados utilizados por las propiedades del control
Delphi (como TxMdAr rowDir) y la declaracion de la interfaz IMdArrowX.
La parte final de la unidad de importacion incluye la declaracion de la clase
TMdArrowX. Se trata de una clase derivada de Tolecontrol que se puede
utilizar para instalar el control en Delphi, como se vio a1 principio de este capitu-
lo. No es necesaria para construir el control ActiveX, solo para instalarlo en
Delphi.
El resto del codigo y el que personalizaremos esta en la unidad principal, que
en el ejemplo se llama MdWArrowImpll. Esta unidad tiene la declaracion del
objeto servidor ActiveX, TMdWArrowX,que hereda de TActiveXControl e
implementa la interfaz especifica IMdWArrowX.Antes de personalizar este con-
trol, conviene ver su funcionamiento.
Primero hay que compilar la biblioteca ActiveX y luego registrarla con la
opcion de menu RuwRegister ActiveX Server de Delphi. Despues se puede
instalar el control como hemos hecho anteriormente, a excepcion de que hay que
especificar un nombre diferente para la nueva clase, para evitar conflictos de
nombres. Si se usa este control, no parecera muy diferente del control VCL origi-
nal, per0 la ventaja es que el mismo componente ahora puede instalarse en otros
entornos de desarrollo.

Aiiadir Nuevas Propiedades


Una vez creado el control ActiveX, aiiadirle nuevas propiedades, eventos o
metodos es sorprendentemente mas simple que hacer la misma operacion para un
compcnente VCL. Delphi proporciona soporte visual especifico para la adicion
de propiedades, metodos o eventos a un control ActiveX, per0 no para un control
VCL.
Se puede abrir la unidad Delphi con la implementacion del control ActiveX y
elegir Edit>Add to Interface. Como alternativa, se puede emplear la misma
orden desde el menu contextual del editor. Delphi abre el cuadro de dialogo Add
To Interface:

R &lax Helper OK
En el cuadro combinado hay que elegir entre una nueva propiedad, metodo o
evento. En el cuadro de edicion podremos escribir entonces la declaracion del
nuevo elemento de la interfaz. Si esta activada la casilla de verificacion Syntax
Helper, aparecera una sugerencia que describe lo que hay que escribir y resalta
10s errores. A1 definir un nuevo elemento de interfaz ActiveX, hay que tener en
cuenta que estamos limitados a 10s tipos de datos COM.
En el ejemplo XArrow hemos aiiadido dos propiedades a1 control ActiveX.
Dado que las propiedades P e n y B r u s h del componente original no son accesi-
bles, hemos hecho accesible su color. Veamos 10s ejemplos que se pueden escribir
en el cuadro de edicion del cuadro de dialogo (ejecutandolo dos veces):
property F i l l c o l o r : I n t e g e r ;
property P e n c o l o r : I n t e g e r ;

Las declaraciones que tenemos que introducir en el cuadro de dialogo Add TO


Interface se aiiaden automaticamente a1 archivo TLB (de la biblioteca de tipos)
del control, a su unidad de importacion de la biblioteca y a su unidad de
implementacion.
Todo lo que tenemos que hacer para completar el control ActiveX es rellenar
10s metodos G e t y S e t de la implementacion. Si ahora instalamos de nuevo el
control ActiveX en Delphi, apareceran las dos nuevas propiedades. El unico pro-
blema es que Delphi utiliza un editor de enteros sin formato que dificulta la
entrada de valores de nuevos colores a mano. Por el contrario, un programa puede
emplear la funcion RGB para crear el valor de color adecuado.
Adicion de una ficha de propiedades
Segun parece, otros entornos de desarrollo poco pueden hacer con nuestro
componente ya que no hemos preparado ninguna pagina de propiedades (ningun
editor de propiedades). Una pagina de propiedades es fundamental para que 10s
programadores que utilizan el control puedan editar sus atributos. Sin embargo,
aiiadir una pagina de propiedades no es tan facil como aiiadir un forinulario con
unos pocos controles. La ficha de propiedades debe integrarse con el entorno de
desarrollo en que se alo.ja. La de nuestro control debe aparecer un cuadro de
dialogo de propiedades del entorno anfitrion, que debe proporcionar 10s botones.
OK, Cancel y Apply y las pestaiias para mostrar varias paginas de propiedades
(algunas de las cuales puede que tambien provengan del entorno anfitrion).
Lo bueno es que Delphi incorpora soporte para fichas de propiedades, por lo
que aiiadirlas requiere muy poco tiempo. Solo hay que abrir un proyecto ActiveX,
abrir el cuadro de dialogo New Items, ir a la ficha ActiveX y escoger P r o p e r t y
P a g e . Aparece algo parecido a un formulario, ya que la c l a s e T P r o p e r t y P a g e 1
(que se crea de forma predeterminada) hereda de la clase T P r o p e r t y P a g e de
la VCL, que a su vez, hereda de TCustomForm.

. . a . .
TRUCO:Del~hioro~orcionacuatro fichas de DroDkdades inteeradas Dara w r

colores, fuentes, imhgenes y cadenas. Los GUID de esas clases se indicarL


con las constantes C l a s s-D C o l o r P r o p P a g e , C l a s s-D F o n t P r o p -
-- -.- - 7 --2 -L .--.-
-..- ---
r a g e , ~ ~-a- s- s - v r l c ~ u r e r r o p-r- ayg e~ ~- -a-uss cs r l g r r o p r a g e-
.. -.a - ,-."L.-2 ..- - - .

en la unidad A c C t r l s .

En la ficha de propiedades podemos aiiadir controles como a un formulario


normal de Delphi y escribir codigo para que 10s controles interactuen. En el e.jem-
plo XArrow, hemos aiiadido a la ficha de propiedad un cuadro combinado con 10s
valores posibles de la propiedad D i r e c t i o n , una casilla de verificacion para la
propiedad F i l l e d , un cuadro de edicion con un control UpDown para estable-
cer la propiedad A r r o w H e i g h t y dos figuras con 10s botones correspondientes
para 10s colores. Se puede ver este formulario en el IDE de Delphi mientras que se
traba.ja con el control ActiveX en la figura 12.9.
El unico codigo aiiadido a1 formulario tiene que ver con 10s dos botones utili-
zados para cambiar el color de las dos figuras, que ofrecen una vista previa de 10s
colores del control ActiveX real. El evento O n C l i c k del boton emplea un com-
ponente C o l o r D i a l o g , como de costumbre:
procedure TPropertyPagel.ButtonPenClick(Sender: TObject);
begin
with ColorDialogl do
begin
Color := ShapePen.8rush.Color;
if Execute then
begin
ShapePen.Brush.Co1or : = Color;
Modified; / / a c t i v a el boton A p p l y
end ;
end;
end :

. . . . . . y
. . . . #. . . .
. . . . . . . . .'. . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Direction. adR~ghf(3)
11 II
.....................................
......................................
.....................................
......................................
.......................................
......................................
.......................................
Pencolor: New... I
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
h o w point color: 0 NW. I

Figura 12.9. El control ActiveX XArrow y su pagina de propiedades, dentro del


entorno de Delphi.

Lo importante de este codigo es la llamada a1 mktodo Modified de la clase


T Proper t yPage.Esta llamada es necesaria para que el cuadro de dialogo de
la ficha de propiedad sepa que hemos modificado uno de 10s valores y tambien
para activar el boton Apply.
Cuando el usuario interactua con uno de 10s otros controles del formulario, la
llamada a M o d i f i e d se hace automaticamente a1 metodo de la clase
TPropertyPage que gestiona el mensaje c m Changed interno.Sin em-
bargo, como usuario no se cambian 10s botonei de estos controles, se necesita
aiiadir esta linea esplicitamente.
3

TRUCO: Otra sugerencia tiene que ver con el Capt ion del formulario de
la ficha de propiedades. Este se utiIizara en el cuadro de diiilogo de propie-
dades del entorno anfitrion como titulo de la solapa correspondiente a la
ficha de propiedades.

El siguiente paso es asociar 10s controles de la ficha de propiedades a las


propiedades reales del control ActiveX. La clase de la ficha de propiedades cum-
ta automaticamente con dos metodos para ello: UpdateOleOb ject y
Updat ePropert y Page.Como sus nombres sugieren, ambos metodos copian
datos desde la ficha de propiedades a1 control ActiveX y viceversa, como se puede
ver en el codigo de ejemplo.
El ultimo paso es conectar la ficha de propiedades en si con el control ActiveX.
Cuando se creo el control, el asistente ActiveX Control Wizard de Delphi aiiadio
automaticamente una declaracion para el metodo Def i n e p r o p e r t y P a g e s en
la unidad de irnplementacion. En este metodo, simplemente hemos llamado a1
metodo D e fi n e P r o p e r t y P a g e (esta vez el nombre del metodo es singular)
para cada ficha de propiedades que queramos aiiadir a1 control. El p a r h e t r o de
este metodo es el GUID de la ficha de propiedades, algo que podemos hallar en la
unidad correspondiente:
p r o c e d u r e TMdWArrowX.DefinePropertyPages(
Definepropertypage: TDefinePropertyPage);
begin
DefinePr~pertyPage(Class~PropertyPagel);
end;

Ya hemos acabado de desarrollar la ficha de propiedades. Despues de volver a


compilar y a registrar la biblioteca ActiveX, ya podemos instalar el control ActiveX
dentro de un entorno de desarrollo anfitrion (incluido Delphi) y ver el aspect0 que
presenta, como puede ver en la anterior figura 12.9.

ActiveForms
Delphi proporciona una alternativa a1 uso del asistente ActiveX Control Wizard
para generar un control ActiveX. Podemos utilizar un ActiveForm que es un
control ActiveX basado en un formulario y que puede albergar uno o mas compo-
nentes de Delphi. Esta tecnica se usa en Visual Basic para crear nuevos controles
y tiene sentido si se quiere crear un componente compuesto.
En el ejemplo XClock, hemos colocado en un ActiveForm una etiqueta (un
control grafico que no puede usarse como punto de partida para un control ActiveX)
y un temporizador, y hemos conectado 10s dos con algo de codigo. El formulariol
control se convierte en un contenedor de otros controles, lo que facilita crear
componentes compuestos (es mas facil que para un componente compuesto VCL).
Para crear un control de este tipo, hay que seleccionar el icono ActiveForm de
la ficha ActiveX del cuadro de dialogo File>New. Delphi pedira informacion en
el cuadro de dialogo ActiveForm Wizard, que es similar a la asistente visto
anteriormente.

lnterioridades de ActiveForm
Antes de continuar con el ejemplo, examinaremos el codigo que genera el
ActiveForm Wizard. La principal diferencia con respecto a un simple formulario
de Delphi esta en la declaracion de la nueva clase formulario, que hereda de la
clase denominada TAct i v e Form e instala una interfaz ActiveForm especifica.
El codigo generado para la clase del formulario activo implementa unos cuantos
metodos G e t y S e t , que cambian o devuelven las propiedades correspondientes
a1 formulario Delphi; este codigo tambien implementa 10s eventos, que vuelven a
ser eventos del formulario.
Los eventos de T Form estan conectados con 10s metodos internos cuando se
crea el formulario. Por ejemplo:
procedure TAXForml.Initialize;
begin
OnActivate : = ActivateEvent;

end ;

Cada evento se proyecta a si mismo sobre el evento del ActiveX externo, como
en el siguiente metodo:
procedure TAXForml.ActivateEvent(Sender: TObject);
begin
if FEvents <> nil then FEvents .OnActivate;
end;

Debido a esta proyeccion no es precis0 controlar 10s eventos del formulario


directamente. En su lugar, se puede aiiadir codigo a estos controladores predeter-
minados o simplemente sobrescribir 10s metodos de T F o r m que terminan Ilaman-
do 10s eventos.
Este problema de la proyeccion tiene que ver solo con 10s ejemplos del propio
formulario, no con 10s eventos de sus componentes. Se pueden seguir controlando
10s eventos de 10s componentes como siempre.

I NOTA: Estos probtemas (y las posibles soluciones) se muestran en el ejem-


plo XForm 1.

El control ActiveX XClock


Ahora que hemos comentado algunos conceptos esenciales, es hora de volver
al desarrollo del ejemplo XClock:
1 . Colocaremos un temporizador y una etiqueta con una fhente grande y texto
centrado sobre el formulario, alienados con el area de cliente.
2. Escribiremos un controlador de eventos para el evento OnTimer del
temporizador, de manera que el control actualice la salida de la etiqueta
con el tiempo actual cada segundo.
procedure TXClock.TimerlTimer(Sender: TObject);
begin
Labell .Caption := TimeToStr (Time);
end ;
3 . Compilaremos esta biblioteca, la registramos y la instalaremos en un pa-
quete para probarla en el entorno Delphi.
Observese el efecto del borde en relieve. Esto esta controlado por la propiedad
AxBorderStyle del formulario activo, una de las pocas propiedades de 10s
formularios activos no disponible para un formulario normal.

ActiveX en paginas Web


En el ejemplo anterior, hemos usado la tecnologia ActiveForm de Delphi para
crear un nuevo control ActiveX. Un ActiveForm es un control ActiveX basado en
un formulario. La documentacion de Borland suele implicar que 10s ActiveForm
deberian utilizarse en paginas HTML, pcro se puede utilizar cualquicr control
ActiveX en una pagina Web. Basicamente, cada vez que se crea una biblioteca
ActiveX, Delphi deberia activar 10s elementos de menu ProjecbWeb
Deployment Options y Project>Web Deploy
- - - - - - - - - .

ADVERTENCIA: Debido a lo que se puede considerar un error, en Delphi


7 estos comandos ~610se activan para un ActiveForm. Si esthn desactivados
se puede usar el truco siguiente: se aiiade un ActiveForm a la biblioteca
ActiveX actual, que habilitara 10s elementos del menu; inmediatamente des-
puts se elimina el ActiveFom y 10s elementos del menu seguirhn estando
disponibles. El problema es que hay que repetir esta operacion cada vez que
se vuelve a abrir el proyecto (a1 menos hasta que Borland solucione el
error).

La primera orden permite especificar donde y como proporcionar 10s archivos


adecuados. En este cuadro de dialog0 se puede dcfinir el directorio del servidor
para desplegar el componente ActiveX, la URL de este directorio y el directorio
del servidor para desplegar el archivo HTML (que tendra una referencia a la
biblioteca Active X mediante el URL proporcionado).
Tambien se puede especificar la utilization de un archivo comprimido CAB,
que puede almacenar el archivo OCX y otros archivos auxiliares, como paquetes,
facilitando la entrega de la aplicacion a1 usuario. Un archivo comprimido signifi-
ca una descarga mas rapida. Hemos generado el archivo HTML y el archivo CAB
para el proyecto XClock en el mismo directorio.
Al abrir este archivo HTML en lnternet Explorer se produce el resultado que
muestra la figura 12.10. Si todo lo que se ve es una cruz roja que indica un fa110
en la descarga del control, existen varias posibles explicaciones para este proble-
ma: Internet Explorer no permite la descarga de controles, no cumple el nivel de
seguridad para el control sin firma, existe una diferencia en el numero de version
del control, y cosas asi.
Delphi 7 A c t i ~ e STest Page
You should see your Delphi 7 forrns or controls embedded in the form below

3 LKto rrqJ M K
Figura 12.10. El control XClock en la pagina HTML de muestra.

Hay que fijarse en que en la parte del archivo HTML que se refiere a1 control
se puede usar la etiqueta especial param para personalizar las propiedades del
control. Por ejemplo, en el archivo HTML del control XArrow, hemos modifica-
d o el a r c h i v o H T M L generado automaticamente (en el archivo
XArrowCus t .htm) con estas tres etiquetas param:

Aunque podria parecer una tecnica util? es importante tener en consideracion


el limitado papel de un formulario ActiveX situado en una pagina Web. Irnplica
permitir que un usuario descargue y ejecute una aplicacion de Windows
personalizada, que conlleva muchas preocupaciones acerca de la seguridad. Un
control ActiveX puede acceder a la informacion del sistema, como el nombre de
usuario, la estructura de directorios y cosas por el estilo. Podriamos decir mas,
per0 no decirlo mejor.

Ademas de crear servidores basicos COM, Delphi tambien permite crear obje-
tos COM mejorados, incluyendo objetos sin estado y soporte de transacciones.
Este tipo de objeto COM fue presentado por Microsoft con las siglas MTS
(Microsoft Transaction Sewer) en Windows NT y 98 y renombrado posterior-
mente como COM+ en Windows 2000lXP. (Aqui hablaremos de COM+, per0 es
lo mismo.) Delphi soporta la creacion de objetos estandar sin estado y modulos de
datos remotos DataSnap basados en objetos sin estado. En ambos casos, empeza-
remos el desarrollo utilizando uno de 10s asistentes disponibles de Delphi, usando
para ello el cuadro de dialog0 New Items y seleccionando el icono Transactional
Object de la ficha ActiveX o el icono Transactional Data Module de la ficha
Multitier. Estos objetos se deben afiadir a un proyecto de biblioteca ActiveX, no a
una aplicacion base. El icono COM+ Event Object se utiliza para soportar 10s
eventos COM+. COM+ ofrece un entorno en tiempo de ejecucion que soporta
servicios de transaccion de bases de datos, seguridad, reserva de recursos y una
mejora global en la robustez de las aplicaciones DCOM. El entorno en tiempo de
ejecucion se encarga de manejar objetos llamados componentes COM+. Se trata
de objetos COM guardados en un servidor en proceso (es decir, una DLL). Mien-
tras que otros objetos COM se ejecutan directamente en la aplicacion cliente, 10s
objetos COM+ se manejan en este entorno en tiempo de ejecucion, en el que se
instalan las bibliotecas COM+. Los objetos COM+ deben soportar interfaces
COM especificas, comenzando por IObjectControl, que es la interfaz base (como
IUnknown para un objeto COM).
Antes de entrar en detalles demasiado tecnicos y de bajo nivel, consideremos
COM+ desde una perspectiva distinta: las ventajas de este enfoque. COM+ pro-
porciona unas cuantas caracteristicas interesantes:
Seguridad basada en funciones: La funcion asignada a un cliente deter-
mina si este tiene el derecho de acceso a una interfaz o a un modulo de
datos.
Recursos de bases de datos reducidos: Se puede reducir el numero de
conexiones a una base de datos, ya que 10s registros de la capa intermedia
se conectan a1 servidor y utilizan las mismas conexiones para varios clien-
tes (aunque no es posible tener mas clientes conectados que licencias para
el servidor).
Transacciones de bases de datos: El soporte COM+ de transacciones
incluye operaciones en bases de datos multiples, aunque pocos servidores
SQL, ademas de 10s de Microsoft, soportan transacciones COM+.
Creacion de un componente COM+
Para crear un componente COM+ debemos comenzar creando el proyecto de
biblioteca de un control ActiveX. DespuCs seguimos estos pasos:
1. Seleccionamos un nuevo Transactional Object en la ficha ActiveX del cua-
dro de dialogo New Items.
2. En el cuadro de dialogo resultante (ver figura 12.1 I), escribimos el nombre
del nuevo componente (ComPlusl Object, en el ejemplo ComPlus 1).

Figura 12.11. El cuadro de dialogo New Transactional Object, utilizado para crear un
objeto COM+.

El cuadro de dialogo New Transactional Object permite escribir el nom-


bre para la clase del objeto COM+, el modelo de hilos (ya que COM+
serializa todas las peticiones, Single o Apartment valdran perfectamente) y
un modelo transaccional:
Requires a Transaction: Indica que cada llamada del cliente a1 servi-
dor es considerada como una transaccion (a menos que el remitente
proporcione un contexto existente de transaccion).
Requires a New Transaction: Indica que cada llamada es considerada
como una nueva transaccion.
Supports Transactions: Indica que el cliente debe proporcionar expli-
citamente un contexto de transaccion.
Does Not Support Transaction: (La seleccion por defecto, y la que
hemos utilizado). Indica que el modulo de datos remoto no participara
en ninguna transaccion. Esta opcion impide que el objeto se active si el
cliente que llama tiene una transaccion.
Ignores Transactions: Indica que el objeto no participa en transaccio-
nes, sin0 que puede usarse sin tener en cuenta si el cliente tiene una
transaccion.
3 . Cuando cerramos este dialogo, Delphi aiiade una biblioteca de tipos y una
unidad de irnplementacion a1 proyecto y abre el editor de la biblioteca de
tipos; donde se puede definir la interfaz del nuevo objeto COM. Para este
ejemplo, hemos aiiadido una propiedad entera Value, un metodo
Increase que tiene como parametro una cantidad y un metodo AsText
que devuelve un Widestring con el valor formateado.
4. Cuando aceptamos las ediciones en el editor de la biblioteca de tipos (ha-
ciendo clic sobre el boton Refresh o cerrando la ventana), Delphi muestra
el asistente Implementation File Update Wizard, per0 solo si se ha activa-
do la opcion Display updates before refreshing de la pagina Type
Library en el cuadro de dialogo Environment Options. Este asistente
pedira confirrnacion antes de aiiadir cuatro metodos a la clase, incluyendo
10s metodos get y set de la propiedad. Ahora se puede escribir algo de
codigo para el objeto COM, que en el ejemplo es bastante trivial.
Una vez que se haya compilado una biblioteca ActiveX o COM, que alberga
un componente COM+, se puede usar la herramienta administrativa Servicios de
componentes (que se muestra en la Microsoft Management Console, o MMC)
para instalar y configurar el componente COM+. Aun mejor, se puede usar el IDE
de Delphi para instalar el componente COM+ mediante la opcion de menu
Run>lnstall COM+ Object. En el cuadro de dialogo que aparecera, se puede
seleccionar el componente a instalar (una biblioteca puede contener varios com-
ponentes) y seleccionar la aplicacion COM+ en que se desea instalar el compo-
nente.

Una aplicacion COM+ no es nada mas que una manera de agrupar componen-
tes COM+; no se trata de un programa ni nada parecido (lo que hace que no quede
claro por que se le llama aplicacion). Por eso, en el cuadro de dialogo Install
COM+ Object, se puede escoger una aplicacion/grupo existente, seleccionar la
pagina Install Into New Application, y escribir un nombre y una descripcion.
Hemos llamado a la aplicacion COM+ La biblia de Delphi 7 Przrebn, tal y
como muestra la figura 12.12 en la consola de administracion de Servicios de
componentes de Microsoft. Este es el terminal que se puede usar para ajustar el
comportamiento de sus componentes COM+, estableciendo su modelo de activa-
cion (activacion en el instante, reserva de objetos, y otros), su soporte de transac-
cion y 10s modelos de seguridad y concurrencia que se desea usar. Tambien se
puede usar esta consola para vigilar 10s objetos y las llamadas de metodos (en
caso de que tarden mucho tiempo en ejecutarse). En la figura 12.12 se puede ver
que hay dos objetos activos.

i@ PI& kch vu Ventma A d - -


,-
. . -- - .~-
&I am
-

I~ ?' 1 ~ - >: * : 1 lo>=,my E


J .- --
-~~ ompansntompansnt~--.- -
-- -- --- - ..-
-~ - . . - -ompansnt

-*
-

r_l Rar & consala


: Servkios & ccmpmentes ~ ddepoqdma_.
. . CLSID- .... . . . . Transat& 1 md.- 5 y f . o ~ .~Moddo..,
- JElupos
!-.
+
" rnK

l
i
Apkadonc. COM+
&, .wrLnmie5
l . ... (3E93EF3-CE24-4
a ~ n r n ~ l u sCmR ... Nose ad ... L Necesar~o Subpro. ..

[i COM+ Expbrcr
i
3 & <OM+ QC Dead Ldrn C
-
E
- COM+ LnY~es
.?$ ~a btbla de D&
+AcCorrponcr*r
:-.
7 (ck~

a CmFlurl .ComFi
*. 'J Interkc5
:+< 1susmpoone
i
, cTe5 n ,
I
. 4-.. -- -- . ---
7-
Figura 12.12. El componente COM+ recien instalado en una aplicacion COM+ (tal y
como lo muestra la herramienta Servicios de componente de Microsoft).

-. - -= ---- - -
1 ADVERTENCIA: Ya que se ha; ireado uno o mits objetw, la bibli&ea I
I COM sigue wgada en el entomo COM+ y alpnos de 10s objctos putden I
P pen0 haya clhtsp conscta-
dos a euos. ror esre mouvo, generameme no se pude rc~ompilmb biblio-
teca COM despds de usarla, a no ser que se use M M C para cmafla o se
I establezca un Transaction Timeout de 0 segundosa MMC. ' I
Hemos creado un programa cliente para el objeto COM+, per0 es igual que
cualquier otro cliente COM de Delphi. Despues de importar la biblioteca de tipos,
que se registra automaticamente mientras se instala el componente, hemos creado
una variable de tip0 de interfaz que hace referencia a ella y llamada por sus
metodos como es habitual.

Modulos de datos transaccionales


A1 crear un modulo de datos transaccional (un modulo de datos remotos dentro
de un componente COM+) nos encontramos con 10s mismos tipos de caracteristi-
Eventos COM+
Las aplicaciones de cliente que utilizan objetos COM tradicionales y servido-
res de Automatizacion pueden llamar a 10s metodos de esos servidores, pero no es
una forma eficiente para comprobar si el servidor ha actualizado 10s datos para el
cliente. Debido a esto, un cliente puede definir un objeto COM que implemente
una interfaz de retrollamada, pasar este objeto a1 servidor y permitir que este lo
llame. Los eventos tradicionales de COM que utilizan la interfaz IConnection-
Point, son simplificados por Delphi para 10s objetos Automation, pero aun asi, su
manipulacion es bastante compleja.
COM+ introduce un modelo de evento simplificado en el cual 10s eventos son
componentes COM+ y el entorno COM+ gestiona las conexiones. En las
retrollamadas tradicionales de COM, el objeto de servidor tiene que hacer el
seguimiento de todos 10s clientes a 10s que se notifica, algo que Delphi no ofrece
de manera automatica (el codigo de evento de Delphi se encuentra limitado a un
unico cliente). Para soportar las retrollamadas de COM para multiples clientes es
necesario aiiadir el codigo para guardar las referencias a cada uno de 10s clientes.
En COM+, el servidor llama a una simple interfaz de evento y el entorno COM+
remite el evento a todos 10s clientes que hayan expresado interes en el. Asi el
cliente y el servidor estan menos acoplados, haciendo posible que un cliente reci-
ba notificacion de diferentes servidores sin cambio alguno en su codigo.
- - - --.- - - --- - -- -- .-
NOTA: Algunos criticos dicen que Microsoft introdujo este modelo s6lo
porque era dificil para 10s desarrolladores de Visual Basic gestionar even-
tos COM del mod0 tradicional. Windows 2000 proporcionaba unas cuantas
caracteristicasnuevas especificamente pensadas para estos desarrolladores.

Para crear un evento COM+, deberiamos crear una biblioteca COM (o biblio-
teca ActiveX) y utilizar el asistente COM+ Event Object. El proyecto resultante
contendra una biblioteca de tipos con la definicion de la interfaz utilizada para
activar 10s eventos ademas de algo de codigo de implementacion falso. El servidor
que reciba la notificacion de 10s eventos proporcionara la implementacion de la
interfaz. El codigo falso se encuentra ahi solo para soportar el sistema de registro
COM de Delphi.
Mientras construiamos la biblioteca MdComEvents, hemos aiiadido a la bi-
blioteca de tipos un metodo sencillo con dos parametros, que han dado lugar a1
siguiente codigo (en el archivo de definicion de la interfaz):
type
IMdInf orm = interface (IDispatch)
[ ' (202D2CC8-8E6C-4E96-9C14-1FAAE392OECC}' ]
p r o c e d u r e Informs(Code: Integer; const Message:
Widestring); safecall;
end;
La unidad principal incluye el objeto COM falso (el metodo es abstracto, de
manera que no tiene implementacion) y su factoria de clase, para permitir que el
servidor se registre a si mismo. Se puede compilar la biblioteca e instalarla en el
entorno COM+, siguiendo estos pasos:
1. En la consola de Servicios de componentes de Microsoft, se escoge una
aplicacion COM+, vamos a la carpeta Componentes y usamos el menu
contextual para aiiadir un nuevo componente.
2. En el asistente para instalacion de componentes COM+, hacemos clic so-
bre el boton Instalar nuevas clases de eventos y seleccionamos la biblio-
teca que acabamos de compilar. La definicion del evento COM+ se instalara
automaticamente.
Para comprobar si funciona, tendremos que crear una implementacion de esta
interfaz de evento y un cliente que la invoque. La implementacion puede aiiadirse
a otra biblioteca ActiveX, albergando un objeto COM basico. Dentro del COM
Object Wizard de Delphi, podemos seleccionar la interfaz a implementar, esco-
giendola de la lista que aparece a1 hacer clic sobre el boton List.
La biblioteca resultante, que en el ejemplo se llama EvtSubscriber, expone
un objeto de Autornatizacion a un objeto COM que implemente la interfaz
IDispatch (que es obligatorio para 10s eventos COM+). El objeto tiene la siguien-
te definicion y codigo:
type
TInformSubscriber = class (TAutoObject, IMdInf orm)
protected
procedure Informs (Code: Integer; const Message:
WideString) ; safecall;
end;

procedure TInformSubscriber.Informs(Code: Integer; const


Message: WideString);
begin
ShowMessage ('Mensaje <' + IntoToStr (Code) + I > : ' +
Message) ;
end;

Despues de compilar esta biblioteca, se puede instalar en primer lugar en el


entorno COM+, y despues enlazarla a1 evento. Este segundo paso se realiza en la
consola de adrninistracion de Servicios de componentes seleccionando la carpeta
Subscripciones dentro del registro del objeto de eventos, y usando el atajo
de menu Nuevo~Subscripcion.En el asistente que aparecera, se debe escoger la
interfaz a implementar (probablemente solo haya una interfaz en la biblioteca de
eventos COM+); se vera una lista de componentes COM+ que implementan esta
interfaz. Escoger uno o mas de ellos, prepara el enlace de la suscripcion, que se
muestra dentro de la carpeta Subscripciones. La figura 12.13 muestra un
ejemplo de la configuracion mientras que se crea este ejemplo.
@ kchm Prodn Ver \&a AWa

+* Bl5 5 -@-I
J Ralr dr r ~ n s d a
- @j Scrvms de componentes w e Id dcntdaz
- _. I Scmdu- -
- JEQUIWS
@$
- gMPc

.+:
_1 A p k a c m z COM+
El UYes
- &ma
-
I
4& r1ptk.a;
+ 6 COM+ Explaer
d @ COM+ QC Dead Lettn C
$ cm+ Mlke5
La M a & Debh 7 (der
-
. O COmpanenteS
8- a cmPCI51c a u
+ '
JLnterfaer

*I - -- - - - - -
17
1
Figura 12.13. Un evento COM+ con dos suscripciones en la consola Servicios de
componentes.

Finalmente, podemos centrarnos en la aplicacion que lanza el evento, que he-


mos llamado Publisher (ya que publica la informacion en la que estan interesados
otros objetos COM). Este es el paso mas simple del proceso, porque se trata de un
sencillo cliente COM que usa el servidor de eventos. Despues de importar la
biblioteca de tipos de eventos COM+, se puede aiiadir a1 codigo de publicacion de
esta manera:
var
Inform: IMdInform:
begin
Inform : = CoMdInform.Create;
Inform. Informs (20, Editl .Text) ;

El ejemplo crea el objeto COM en el metodo Formcreate para mantener


presente la referencia, per0 el efecto es el mismo.
Ahora el programa cliente piensa que esta llamando a1 objeto del evento COM+,
per0 este objeto (ofrecido por el entorno COM+) llama a1 metodo para cada uno
de 10s suscriptores activos. En este caso se acabara viendo este cuadro de men-
saje:

I Message <20>: Here Iam


I
Para hacer las cosas mas interesantes, se puede suscribir dos veces el mismo
servidor a la interfaz de eventos. El efecto global es que sin retocar el codigo del
cliente se conseguiran dos cuadros de mensaje, uno por cada servidor suscrito.
Obviamente este efecto pasa a ser interesante cuando se tiene multiples compo-
nentes COM distintos que pueden controlar el evento, ya que se pueden habilitar
e inhabilitar con facilidad desde la consola de administracion, modificando el
entorno COM+ sin modificar el codigo del programa.

COM y .NET en Delphi 7


Mientras que desarrollaba la nueva infraestructura .NET, Microsoft ha inten-
tad0 ayudar a las empresas que continuan sus programas ya existentes. Uno de
estos caminos de adaptacion esta representado por la compatibilidad de 10s obje-
tos .NET con 10s objetos COM. Se puede usar un objeto COM existente dentro de
una aplicacion .NET, aunque no en el ambito del codigo gestionado y seguro.
Tambien se pueden usar 10s ensamblajes .NET de las aplicaciones de Windows
como si fueran objetos COM nativos. Esta funcionalidad tiene lugar gracias a 10s
recubrimientos proporcionados por Microsoft.
La afirmacion de Borland de soportar la interoperabilidad COM/.NET en Delphi
7 es principalmente una referencia al hecho de que 10s objetos COM compilados
con Delphi no crearan problemas para el importador .NET. Ademas, el importa-
dor de la biblioteca de tipos de Delphi puede trabajar sin trabas tanto con 10s
ensamblajes de .NET como con las bibliotecas COM estandar.
Dicho esto, a menos que se disponga de una inversion extensa en COM, no es
aconsejable seguir este camino. Si se quiere apostar por las tecnologias de
Microsoft, el futuro esta en las soluciones .NET nativas. Si no gustan las tecnolo-
gias de Microsoft o se desea una solucion multiplataforma, COM seguira siendo
una eleccion peor que .NET (en el futuro puede que tengamos un marco de trabajo
.NET para otros sistemas operativos).

1; TRUCO: Los pasos--suge~dbsdeberian funCibna~tambitn en Delphi 5 , . I


Delphi 7 aiiad; un s i s t e k de irnportaciirn automitico que a veces tiene
problemas con p a r k del codigo generado pqr'el compilador d q Delphi for
5 .NET Preview.
.. ,( 1
Para demostrar las caracteristicas de importacion de .NET, hemos creado una
biblioteca .NET con una interfaz y una clase que la implementa. La interfaz y la
clase se parecen a las del ejemplo FirstCom ya comentado. Este es el codigo de la
biblioteca, que debe compilarse con el compilador de Delphi for .NET Preview.
Hay que crear un objeto, o el enlazador eliminara casi todo de la biblioteca com-
pilada (ensamblaje, en la jerga de .NET):
library NetLibrary

uses
NetNurnberClass in 'NetNumberClass.pasl;
begin
/ / c r e a u n o b j e t o p a r a e n l a z a r t o d o e l codigo
TNumber.Create;
end.

El codigo se encuentra en la unidad NetNumberClass, que define una interfaz


y una clase que la implementa.
type
INumber = interface
function GetValue: Integer;
procedure SetValue (New: Integer) ;
procedure Increase;
end ;

TNurnber = class(TObject, INumber);


private
fValue: Integer;
public
constructor Create;
function GetValue: Integer;
procedure SetValue (New: Integer) ;
procedure Increase;
end ;

Hay que fijarse en que, a1 contrario que un servidor COM, la interfaz no


necesita un GUID, de acuerdo con las reglas de .NET (aunque puede tener uno
usando un atributo de la clase G u i d A t t r i b u t e ) . El sistema generara
automaticamente uno. Despues de compilar este codigo (disponible en la carpeta
N e t I m p o r t del codigo de este capitulo) con Delphi for .NET Preview (in0 con
Delphi 7!), se necesita realizar dos pasos: en primer lugar, ejecutar . N E T
Framework A s s e m b l y R e g i s t r a t i o n U t i l i t y d e M i c r o s o f t ( r e g a s m ) ;
en segundo lugar, ejecutar T y p e L i b r a r y I m p o r t e r d e B o r l a n d
(tlibimp). En teoria deberiamos poder saltarnos este paso y usar directamente el
cuadro de dialog0 Import Type Library, per0 con algunas bibliotecas el uso del
programa tlibimp es necesario. En la practica, hay que ir a la carpeta en que se
haya compilado la biblioteca y escribir en la linea de comandos 10s dos comandos
en negrita (deberia verse como resultado el resto del texto capturado aqui):
D:\md7code\l2\NetImport>regasm NetLibrary.dl1
Microsoft (R) .NET Framework Assembly Registration Utility
1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights
reserved.

Types registered successfully

D:\md7code\l2\NetImport>tlibimp NetLibrary.dl1
Borland TLIBIMP Version 7.0
Copyright (c) 1997, 2002 Borland Software Corporation
Type library loaded . . . .
Created D:\rnd7code\l2\NetImport\mscorlib~TLBBdcr
Created D:\md7code\12\NetImport\mscorlib~TLBBpas
Created D:\md7code\12\NetImport\NetLibrary~TLBBdcr
Created D:\rnd7code\l2\NetImport\NetLibrary~TLBBpas

El efecto es crear una unidad para la biblioteca de tipos del proyecto y una
unidad para la Microsoft .NET Core Library importada ( m s c o r l i b .d l 1).Ahora
podemos crear una nueva aplicacion de Delphi 7 (un programa Win32 estandar) y
usar 10s objetos .NET como si fueran objetos COM. Este es el codigo del ejemplo
NetImport, que se muestra en la figura 12.14:
uses
NetLibrary-TLB;

procedure TForml.btnAddClick(Sender: TObject);


var
num: INumber;
begin
num : = CoTNumber .Create as INumber;
num.Increase;
ShowMessage (IntToStr (num.GetValue)) ;
end;

Add mrnberr in d NET dbied


=
I

Figura 12.14. El programa Netlmport usa un objeto .NET para sumar nlimeros
Parte Ill
Arquitecturas
orientadas
a bases de
datos en Delphi
Arquitectura
de bases de
datos Delphi

El soporte de Delphi para aplicaciones de bases de datos es una de las caracte-


risticas clave del entorno de programacion. Muchos programadores pasan la ma-
yor parte de su tiempo escribiendo codigo de acceso a 10s datos, que necesita ser
la parte mas robusta de una aplicacion de bases de datos. En este capitulo vere-
mos como funciona el soporte que ofrece Delphi para la programacion con bases
de datos.
Lo que no se vera aqui es una explicacion sobre la teoria del diseiio de bases de
datos. Supondremos que ya conoce 10s fundamentos de este diseiio y que ya ha
diseiiado la estructura de una base datos. No entraremos en problemas especifi-
cos de bases de datos; el objetivo es ayudar a comprender como soporta Delphi el
acceso a bases de datos.
Comenzaremos con una explicacion de las distintas alternativas que Delphi
ofrece en cuanto a acceso a datos, y despues contemplaremos una vision global de
10s componentes de bases de datos disponibles en Delphi. Este capitulo se centra
en el uso del componente TC 1i e n t D a t S e t para acceder a 10s datos locales, sin
hacer caso a1 acceso clientelservidor (comentado en otro capitulo). Tambien ha-
blaremos sobre la clase T D a t a S e t , analizaremos en profundidad los componen-
tes T F i e l d y el uso de 10s controles data-aware.
Finalmente, hay que tener en cuenta que casi todo lo comentado en este capitu-
lo se podra aplicar a diversas plataformas. En particular, 10s ejemplos pueden
adaptarse a CLX y a Linux recompilandolos y haciendo referencia a archivos
CDS en las carpetas apropiadas. Este capitulo trata 10s siguientes temas:
Componentes de bases de datos en Delphi.
Alternativas de acceso a bases de datos.
Uso de controles data-aware.
El control DBGrid.
Manipulation de campos de tablas.
Aplicaciones de bases de datos con controles estandar.

Acceso a bases de datos: dbExpress, datos


locales y otras alternativas
En las primeras versiones de Delphi (adoptado inmediatamente como herra-
mientas para crear aplicaciones orientadas a bases de datos), la unica tecnologia
disponible para acceder a datos de bases de datos era utilizar el Borland Database
Engrne (BDE). Desde Delphi 3 , la parte de la VCL relacionada con el acceso a
bases de datos se ha reestructurado para abrirla mas a diversas soluciones de
acceso a bases de datos entre las que en la actualidad se incluye ADO, componen-
tes InterBase nativos, la biblioteca dbExpress y BDE.
Muchas terceras partes ofrecen mecanismos alternativos de acceso a bases de
datos para una amplia variedad de formatos de datos (aunque algunos no Sean
accesibles como componentes de Borland) y ofrecen aun asi una solucion integra-
da con la VCL de Delphi.
. .

TRUCO:En Kylix, la perspectiva general es ligerarnente distinta. Borland


ha decidido no adaptar la antigua tecnologia BDE a Linux y en su lugar se
ha centrado en urn nueva fina capa de acceso a bases de datos, dbExprcss.

Como una solucion mas, para las aplicaciones simples puede usarse el compo-
nente ClientDataSet de Delphi, que permite guardar tablas en archivos locales
1
(algo que Borland llama MyBase). Fijese en que una tipica aplicacion de Delphi
basada en tablas Paradox no puede adaptarse a Kylix, debido a su carencia de
BDE.

La biblioteca dbExpress
Una de las caracteristicas mas importantes nuevas en Delphi en 10s aiios mas
recientes es la introduccion de la biblioteca de la base de datos dbExpress (DBX),
disponible tanto para Linux como para Windows. Se trata de una biblioteca y no
de un motor de bases de datos porque, a1 contrario que otras soluciones, dbExpress
utiliza un enfoque ligero y basicamente no necesita ninguna configuration en las
maquinas de 10s usuarios finales.
Las caracteristicas claves de dbExpress y las razones por las que Borland la
ha introducido, junto con el desarrollo del proyecto Kylix son su ligereza y
portabilidad. En comparacion con otras bases de datos muy potentes, dbExpress
resulta algo limitada en cuanto a sus capacidades.
dbEspress solo puede acceder a servidores SQL (no a archivos locales); no
tiene capacidad para guardar copias temporales para acelerar 10s procesos y solo
proporciona un acceso a datos unidireccional. Solo puede trabajar originariamen-
te con consultas SQL y no puede crear las sentencias de actualizacion SQL co-
rrespondientes.
La primera impresion que producen estas limitaciones es que la biblioteca
podria resultar inutil. Nada mas lejos de la realidad: se trata de caracteristicas
que la hacen interesante. Los con.juntos de datos unidireccionales sin actualiza-
cion directa son lo normal si se necesita generar informes, incluyendo la genera-
cion de paginas HTML que muestren el contenido de una base de datos. Sin
embargo, si se desea construir una interfaz de usuario para editar 10s datos, Delphi
incluye componentes especificos (ClientDataSet y Provider, para ser precisos)
que permiten la edicion en cache y la resolucion de consultas SQL.
Estos componentes permiten que la aplicacion basada en dbExpress tenga un
mayor control del que podemos tener con un motor de base de datos independiente
(monolitico), que realiza automaticamente acciones adicionales de un mod0 no
personalizable.
dbEspress permite escribir una aplicacion que, escepto 10s problemas deriva-
dos de 10s diversos dialectos SQL, puede acceder a muchos motores de bases de
datos distintos sin realizar mucha modification del codigo. Entre 10s servidores
SQL soportados en Delphi 7 se incluye la propia base de datos InterBase de
Borland, el servidor de bases de datos de Oracle, la base de datos MySQL (que es
muy popular en Linux), Informix, DB2 de IBM, y SQL Server de Microsoft. En
este capitulo vamos a centrarnos en las bases de la arquitectura de las bases de
datos.

TRUCO:La dis@bdidsrd de un controlador dbExpress para SQL Server


de Microsoft en flelphi 7 Il'& & huec6 sigdficativo.ksta base de datos se
suele usar ed la p
w r
i
m aWindo~&s,y 10s desjutoiladores que aecesitaban
ma solucih f~cfh&W3 ttailspo~'te entre disthtos senridores de bases de
datos solfan ten& $Ire incluir el sopotte para SQL Server. Ahora existe una
razbn rnenos para &r que seguir utilizaBdo BDE. Borland ha publicado
una actualitaci6n del controlador &Express de SQL Server que se incluye
con Delphi 7 para solucionar un par de Mectos.
Borland Database Engine (BDE)
Borland aun incluye BDE, que permite acceder a formatos de bases de datos
locales (como Paradox y dBase) y servidores SQL a1 igual que a cualquier siste-
ma accesible a traves de controladores ODBC. Se trataba de la tecnologia de
bases de datos estandar en las primeras versiones de Delphi, per0 ahora Borland
considera que esta obsoleta. Esto resulta especialmente cierto para el uso de BDE
para el acceso a servidores SQL a traves de 10s controladores SQL Link. Utilizar
BDE para acceder a tablas locales sigue apoyandose de manera oficial, simple-
mente porque Borland no ofrece una ruta de migracion directa para este tip0 de
aplicacion.
En algunos casos, una tabla local puede sustituirse por el componente
C 1ie nt Da t a S e t (MyBase) de manera especifica para tablas de busqueda pe-
queiias y temporales. Sin embargo, este enfoque no funcionara para tablas locales
mayores, ya que MyBase necesita que se cargue toda la tabla en memoria para
acceder aunque sea a un unico registro. Una posible sugerencia es llevar las
tablas mas grandes a un sewidor SQL instalado en el ordenador cliente. InterBase,
con su pequeiio uso de recursos, resulta ideal para este tip0 de situacion. Esta
clase de migracion tambien abre las puertas a Linux, donde BDE no se encuentra
disponible.
Por supuesto, si aun se tienen aplicaciones que utilicen BDE, se pueden seguir
utilizando. La pagina BDE de l a c o m p o n e n t Palette de Delphi aun tiene Table,
Query, StoreProc y otros componentes especificos de BDE. No es aconsejable
desarrollar nuevos programas con esta tecnologia tan anticuada, que no ya apoya
su creador. Se deberian buscar motores de terceras partes para sustituir a BDE
cuando el programa a desarrollar necesite una arquitectura similar (o se necesite
compatibilidad con formatos de archivos de bases de datos antiguos).

NOTA: Por este motivo no se encontrara en este libro ninguna explicacion


sobre BDE.

InterBase Express (IBX)


Borland ha creado otro conjunto de componentes de acceso a bases de datos
para Delphi: InterBase Express (IBX). Estos componentes estan adaptados
especificamente a1 servidor propio de Borland, InterBase. A1 contrario que
dbExpress, no se trata de un motor de bases de datos independiente del servidor,
sino de un conjunto de componentes para acceder a un sewidor de bases de datos
especifico. Si se tiene pensado usar unicamente InterBase como sistema de res-
paldo de la base de datos, usar un conjunto especifico de componentes puede
proporcionar mas control sobre el servidor, conseguir el mejor rendimiento posi-
ble y permitir una configuracion y mantenimiento del servidor desde la propia
aplicacion cliente personalizada.

Se puede considerar el uso de IBX (u otro conjunto de componentes compara-


ble) si se esta seguro de que no se cambiara la base de datos y se quiere conseguir
el mejor rendimiento y control posible a costa de la flexibilidad y la transpor-
tabilidad. La parte negativa es que el rendimiento adicional y el control consegui-
dos pueden ser limitados. Tambien habra que aprender a utilizar otro conjunto de
componentes con un comportamiento especifico, en lugar de aprender a utilizar
un motor generic0 y aplicar ese conocimiento a distintas situaciones.

MyBase y el componente ClientDataSet


C l i e n t Da t a S e t es una base de datos que accede a datos que se conservan
cn mcmoria. Estos datos pueden ser temporales (creados por el programa y borra-
dos a1 cerrarlo), cargados desde un archivo local y vueltos a guardar en el: o
importados por otro conjunto de datos mediante un componente P r o v i d e r .
Borland sugiere que se deberia usar el componente C l i e n t Da t a S e t pro-
yectado sobre un archivo con el nombre MyBase, para indicar que puede conside-
rarse como una solucion de base de datos local.
El acceso a 10s datos desde un proveedor se trata de un enfoque habitual para
las arquitecturas clientelservidor y multicapa. El componente C l i e n t D a t a S e t
resulta particularmente util si 10s componentes de acceso a datos que se usan
ofrecen un almacenamiento temporal (cache) limitado o nulo, como en el caso del
motor dbExpress.

dbGo para ADO


ADO (ActiveX Data Objects), es la interfaz de alto nivel de Microsoft para
acceder a bases de datos. ADO se implements en la tecnologia OLE DB de acceso
a datos de Microsoft, que ofrece acceso a las bases de datos relacionales o no
relacionales asi como a correo electronico, sistemas de archivos y objetos de
negocio personalizados. ADO es un motor con caracteristicas comparables a las
de BDE: independencia de servidor de bases de datos que soporta servidores
locales y SQL por igual, un motor realmente potente y una configuracion sim-
plificada (porque no es centralizada). En teoria, la instalacion no deberia repre-
sentar un problema, puesto que el motor forma parte de las versiones recientes de
Windows. Sin embargo. la compatibilidad limitada entre versiones de ADO nos
obligara a 10s usuarios a actualizar sus ordenadores segun la version con la que se
creo el programa. El gran tamaiio de la instalacion de MDAC (M~crosoftData
Access Componentes), que actualiza grandes p a t e s del sistema operativo, hace
que esta tarea no resulte nada sencilla.
ADO ofrece ventajas concretas si queremos usar un servidor Access o SQL,
puesto que 10s controladores de Microsoft para sus propias bases de datos poseen
una calidad superior a 10s proveedores promedio de OLE DB. Para las bases de
datos Access. sobre todo, utilizar 10s componentes ADO de Delphi es una buena
solution. Pero si estamos pensando en usar otros servidores SQL, primer0 tendre-
mos que asegurarnos de que haya disponibles controladores de buena calidad.
ADO es muy potente, pero hay que aprender a convivir con el, porque esta en
medio de nuestro programa y la base de datos, ofreciendo servicios pero tambiin
ocasionalmente dando ordenes diferentes a las esperadas. Por otra parte, tambien
hay algo negativo. no se puede ni siquiera pensar en usar ADO si planeamos
realizar un futuro desarrollo multiplataforma: esta tecnologia especifica de
Microsoft no esta disponible en Linux ni en otros sistemas operativos.
So10 cs recomendable usar ADO si solo te tiene pensado trabajar con Windows?
sc quierc usar Access LI otras bases de datos de Microsoft, o se encuentra un buen
provcedor de OLE DB para cada uno de 10s servidores de bases de datos con que
se tiene planeado trabajar (por el momento. esto escluye a InterBase y muchos
otros servidores SQL).
Los componcntes ADO (partc de un paquete Borland denominado dbGo) estan
agrupados en la ficha ADO de la Component Palette. Los tres componentes
principales son ADOConncction (para concxion a bases de datos), ADOCommand
(para ejccutar ordenes SQL) y ADODataSet (para ejecutar peticiones que devuel-
ven un conjunto de resultado). Tambien hay tres componentes de compatibilidad
(ADOTable, ADOQuery y ADOStoredProc) que podemos usar para modificar
aplicaciones basadas en el BDE en ADO. Por tiltimo, esta el componente
RDSConncction, para acceder a datos en aplicaciones multicapa remotas.

NOTA: Microsoft eski sustituyendo ADO por su version .NET,que


basa en Ias mismas ideas centrales. Por eso, usar ADO podria
r
ruts
I . I . . I -
comoaa nacla las apllcaclones .,mm .. I
I
. * 3 .-

MyBase: ClientDataSet independiente


Si se desea escribir una aplicacion de base de datos monousuario en Delphi, el
enfoque mas sencillo es usar el componente ClientDataSet y proyectarlo
sobre un archivo local. La proyeccion de este archivo local es distinta de la pro-
yeccion de datos tradicionales sobre un archivo local. El enfoque tradicional con-
siste en leer desde el archivo un registro cada vez y posiblemente disponer de un
segundo archivo que almaccnc 10s indices. El client DataSet proyecta toda
una tabla (y posiblemente una estructura maestroldetalle) sobre el archivo por
completo: cuando se inicia un programa. sc carga en mcmoria el archivo completo
y se guarda todo al mismo tiempo.

ADVERTENCIA: Esto explica que no se pueda usar este enfoque en una


situation multiusuario o multiaplicacion. Si dos programas o dos instan-
cias del mismo programa cargan el mismo archivo ClientDataSet en
memoria y moairlcan 10saatos, la umma tama guaraaaa soorescrmira' 10s
J'C. 1 J A I 'lr' L I 1 I 1 1 .?. 1

cambios realizados por otros programas.

Este soporte para la permanencia del contenido de un ClientDataSet se creo


hace unos cuantos afios como un mod0 de implementar el llamado modelo de
maletin. Un usuario podia (y sigue pudiendo) descargar datos de su servidor de
bases de datos a1 cliente, guardar algunos de 10s datos, trabajar en desconesion
(mientras viaja con un portatil, por ejemplo) y volver a conectarse finalmente
para enviar 10s cambios.

Conexion a una tabla local ya existente


Para proyectar un ClientDataSet sobre un archivo local, hay que e a r su pro-
piedad FileName. Para .crear un programa minimo (llamado MyBase 1 en el
ejemplo). lo unico que se necesita es un componente Client DataSet conecta-
do a un archivo CDS (esisten unos cuantos en la carpeta Data disponible en
\Archives de programa\Archivos Comunes\Borland Shared):
un DataSource y un control DBGrid. El client DataSet se conecta con el
DataSource mediante la propiedad DataSet del DataSource y el DataSource a1
DBGrid mediantc la propiedad DataSource de la cuadricula, como muestra el
listado 13.1. En este punto se activa la propiedad Active del ClientDataSet y se
dispondra de un programa que mostrara datos dc un base de datos incluso en
tiempo de disefio, como muestra la figura 13.1.

Listado 13.1. El archivo DFM del programa de muestra MyBasel

o b j e c t Forml: TForml
Activecontrol = DBGridl
Caption = ' M y B a s e l '
OnCreate = Formcreate
o b j e c t DBGrid: TDBGrid
DataSource = DataSourcel
end
o b j e c t DataSourcel: TDataSource
DataSet cds
end
o b j e c t cds: TClientDataSet
FileName = ' C: \Archives d e p r o g r a m \Archives
Comunes \ B o r l a n d Shared\Da t a \ C u s t o m e r . c d s '
end
end

1510 Ocean Pi

1551 M m l D

1624 MakaiSC
1645 Action Ck
1651 Jamaica L ,,- ,.,
1680 l s l d Finders 6133 1/3 Stone Avenue
1984 Adventure Undersea W Box 744
2118 Blue Spalo Ckrb 63365 Nez Perce S l m t
2135 Frank's Divers Supply 1455 North 44th St.
I d
l
Figura 13.1. Una tabla local de rnuestra activa en tiempo de disefio dentro del IDE de
Delphi.

Cuando se hacen cambios y se cierra la aplicacion, 10s datos se guardan


automaticamente en el archivo. (Podria quererse desactivar el registro de cam-
bios, del que ya hablaremos, para reducir el tamaiio de estos datos.) El conjunto
de datos tambien dispone de un metodo s a v e T o F i 1e y un metodo
L o a d F r o m F i l e que se pueden usar en el codigo.
Se ha inhabilitado el c l i e n t Dataset en tiempo de diseiio para evitar in-
cluir todos sus datos en el archivo DFM del programa y en el archivo ejecutable
compilado, prefiriendo mantener 10s datos en un archivo independiente. Para ha-
cer esto; es necesario cerrar el conjunto de datos en tiempo de diseiio, despues de
comprobarlo, y aiiadir una linea a1 evento o n c r e a t e del formulario para
abrirlo:
procedure TForml.FormCreate (Sender: T O b j e c t ) ;
begin
c d s .Open;
end;
De la DLL Midas a la unidad MidasLib
Para ejecutar una aplicacion que use el componente C l i e n t D a t a S e t , tam-
bien se necesita distribuir la biblioteca dinamica m i d a s . d l 1 a que hace referen-
cia la unidad D S I n t f .p a s . El codigo central del componente C l i e n t D a t a S e t
no forma directamente parte de la VCL y no se encuentra disponible en forma de
codigo fuente. Es una lastima, porque muchos desarrolladores estan acostumbrados
a depurar el codigo fuente de la VCL y usarlo como referencia definitiva.

ADVERTENCIA: La bibliotecamidas .dl1 no tiene un numero de ver-


sion en sn nambre. Por eso, si un ordenador tiene una version mhs antigua,

I tarse correctamente.
I
La bibliotcca Midas es una biblioteca en lenguaje C, per0 desde Delphi 6
puede enlazarse directamente con un ejecutable a1 incluir la unidad M i d a s L i b
(una DCU especial producida por un compilador de C). En este caso, no sera
necesario distribuir la biblioteca en formato DLL.

Formatos XML y CDS


El componente C l i e n t D a t a S e t soporta dos formatos de streaming distin-
tos: el formato nativo y un formato basado en XML. La carpeta B o r l a n d
S h a r e d \ D e m o ya comentada contiene versiones de un cierto numero de tablas
en cada uno de 10s dos formatos. De manera predefinida, MyBase guarda las
bases de datos en formato XML. El metodo S a v e T o F i l e tiene un parametro
que permite especificar el formato, y el metodo L o a d F r o m F i l e funciona
automaticamente con ambos'formatos.
A1 usar el formato X M L se tiene la ventaja de conseguir que 10s datos perma-
nentes resulten accesibles tambien mediante un editor y otros programas que no
esten basados en el componente C1i e n t D a t a S e t . Sin embargo, este enfoque
implica la conversion constante de 10s datos, ya que el formato CDS es tan similar
a la representacion en memoria interna que utiliza invariablemente el componen-
te, sin importar el formato de streaming. Ademas, el formato XML produce archi-
vos mas grandes, ya que estan basados en testo. Como promedio, un archivo
X M L de MyBase es el doble de grande que el archivo CDS correspondiente.
. - 7 - - -

TRUCO: Mientras que un Client D a t aset se encuentra en memoria,


se puede extraer su representacibn XML mediante la propiedad X M L D a t a
sin guardar 10s datos en un archivo. Una muestra de esto se vera en el
siguiente ejernplo.
Definicion de una tabla local nueva
Ademas de permitir la conexion con una tabla de base de datos ya existente
almacenada en un archivo local, el componente C 1i en t Da t a Se t permite crear
facilmente tablas nuevas. Todo lo que hay que hacer es usar su propiedad
F i e l d D e f s para definir la estructura de la tabla. Despues de esto, se puede
crear fisicamente el archivo para la tabla mediante el comando Create DataSet en
el menu de metodo abreviado del componente C l i e n t D a t a S e t en el IDE de
Delphi o usar el metodo C r e a t e D a t a S e t en tiempo de ejecucion.
El siguiente listado es un fragment0 del archivo DFM del ejemplo MyBase2,
que define una tabla de base de datos local nueva:
object ClientDataSetl: TClientDataSet
FileName = ' m y b a s e 2 . c d s 1
FieldDefs = <
item
Name = ' one '
DataType = ftString
Size = 20
end
item
Name = ' two'
DataType = ftsmallint
end>
StoreDefs = True
end

Hay que fijarse en la propiedad S t o r e De f s , que se fija automaticamente


como T r u e cuando se edita el conjunto de definiciones de campos. De manera
predeterminada, un conjunto de datos en Delphi carga sus metadatos antes de
abrirse. Solo se utiliza estos metadatos locales si se almacena una definition local
en el archivo DFM (guardar las definiciones de campos en el archivo DFM tam-
bien ayuda a crear una cache para estos metadatos en una arquitectura clientel
servidor) .
Para tratar la creacion del conjunto de datos opcional, la desactivacion del
registro y la representacion de la version XML de 10s datos iniciales en un control
Memo, la clase del formulario del programa tiene el siguiente controlador para el
evento O n c r e a t e :
p r o c e d u r e TForml.FormCreate(Sender: TObject);
begin
.
i f n o t FileExists (cds FileName) t h e n
cds-CreateDatSet;:
cds .Open;
cds-MergeChangeLog;
cds .Logchanges : = False;
Memol.Lines.Text : = StringReplace (
C d s - X M L D a t a , ' > I , ' > ' + sLineBreak, [rfReplaceAll]);
end ;
La ultima sentencia incluye una llamada a S t r i n g R e-p l a c e como una espe-
-

cie de formateo XML para pobres: el c6digo aiiade una nueva linea a1 final.de
cada etiqueta XML afiadiendo una nueva liiea tras la marca de cierre. La figura
13.2 muestra la representacion XML con unos cuantos registros.

<PARAMS/)
</METADATA>
<ROWDATA>
<ROW om-"one" Iwo="lU/>
<ROW one.'koMIwo="2"/>
<ROWm="ne"lwa-"lV'l,
</ROWDATA)
t/DATAPACMT>

Figura 13.2. La representacion XML de un archivo CDS en el ejernplo MyBase2. La


estructura de tabla se define en el prograrna, que crea un archivo para el conjunto de
datos durante su prirnera ejecucion.

lndexado
Una vez que se ticne un C l i e n t D a t a S e t en mcmoria, se pueden realizar
muchas operaciones sobre el. Las mas simples son el indexado, filtrado y busque-
da de registros; entre las operaciones mas complejas se incluyen la agrupacion, la
definicion de valores agregados y la gestion del registro de cambios. Dejaremos
10s temas mas compkjos para el final de capitulo.
Indexar un C l i e n t D a t a S e t es una cuestion de establecer la propiedad
I n d e x F i e l d N a m e s . Suele hacerse cuando el usuario hace clic sobre el campo
de titulo en un componente DBGrid (con lo que se lanza el evento O n T i t l e -
c l i c k ) , como en el ejemplo MyBase2:

procedure TForml.DbGridlTitleClic(Colurnn: TColumn);


begin
cds.IndexFieldNames : = Column.Field.FieldName;
end;

A1 contrario que otras bases de datos locales, c 1i e n t D a t a s e t puede tener


este tipo de indexado dinamico sin ninguna configuracion de la base de datos ya
que 10s indices sc calculan en memoria.

TRUCO: El componente tambikn soporta indices basados en un campo


calculado, mas concretamente en un campo calculado internamente, solo
.r -
disponible para kste o n o h
lados normales, que se calculan cada vez que se usa el registro, 10s val'ores
A
uu
n 1ne rramnna ~rrln-m.l-Arre ;n+nmnmnn+aJ
~ V w
Ja x u p r a w a a u u a a u w ~
c- fiat-am
W
IUC~ILLZULIUUCVtan una sola vez y se man-
v a a v u J

tienen en memoria. Por este motivo, 10s indices 10s consideran como sim-
ples campos.

Ademas de asignar un nuevo valor a la propiedad IndexFieldNames,se


puedc definir un indice mediante la propiedad IndexDefs . De esta manera se
pueden definir varios indices y mantenerlos todos en memoria, conmutando aun
mas rapido de uno a otro.

TRUCO: Definir un indice separado es el unico modo de tener un indice


descendente, en lugar de un indice ascendente.

Filtrado
Al igual que con cualquier otro conjunto de datos, podemos usar la propiedad
Filter para especificar la inclusion en el conjunto de datos de partes de 10s
datos a las que esta ligado el componente. La operation de filtrado ocupa memo-
ria despues de cargar todos 10s registros, asi que se trata de una manera de mos-
trar menos datos al usuario, no de limitar la ocupacion de memoria de un conjunto
de datos local grande.
Cuando queremos conseguir una gran cantidad de datos desde un servidor (en
una arquitectura clientelservidor) deberiamos usar una consulta adecuada de mod0
que no recuperemos un gran conjunto de datos de un servidor SQL. La mejor
opcion deberia ser normalmente el filtrado desde la salida del servidor. Con 10s
datos locales, se puede tener en cuenta el partir un gran numero de registros en un
conjunto de distintos archivos, para que se puedan cargar solo 10s necesarios y no
todos.
Sin embargo, el filtrado local en el ClientDataSet puede resultar muy
util, sobre todo porque las espresiones de filtro que podemos usar con este com-
ponente son mucho mas amplias que aquellas que podemos usar con otros conjun-
tos dc datos. En concreto, podemos usar lo siguiente:
La comparacion estandar y 10s operadores Iogicos ( ~ o p 1uation > 1000
and Area < 1000).
Operadores aritmeticos ( Population / Area < 10 ) .
Funciones de cadena (Substring(Last-Name, 1, 2 ) = Ca ' )
Funcionesdefechayhora (Year (Invoice-Date) = 2002)
Otras, como la funcion Like, comodines y un operador In.
Estas prestaciones de filtrado se encuentran perfectamente documentadas en el
archivo de ayuda de la VCL: Deberia buscarse la pagina "Limiting what records
appear" vinculada a la descripcion de la propiedad F i l t e r de la clase
T C l i e n t DataSet, o llegar a ella desde la pagina Help Contents, siguiendo
esta cadena: Developing Database ApplicationsAJsing client datasets>
Limiting what records appear.

Busqueda de registros
El filtrado permite limitar 10s registros que se muestran a1 usuario del progra-
ma, per0 muchas veces se querran mostrar todos 10s registros y acceder unica-
mente a uno especifico. El metodo Locate se encarga de esto. Si jamas se ha
usado Locate, un primer vistazo a1 archivo de ayuda no dejara las cosas muy
claras. La idea es que hay que proporcionar una lista de 10s campos que se quieren
buscar y una lista de valores, uno para cada campo. Si se desea buscar una
correspondencia con un unico campo, el valor se pasa directamente. como en este
caso en que la cadena de busqueda se encuentra en el componente EditName):
procedure TForml.btnLocateClick(Sender: TObject);
begin
i f not cds .Locate ( ' L a s t N a m e l, EditName. Text, [I ) then
MessageDlg ( ' " ' + EditName.Text + ' " not f o u n d ' , mtError,
[ r n b o k l , 0);
end;

Si se busca mediante varios campos, hay que pasar una matriz variante con la
lista de valores para 10s que se desea correspondencia. La matriz variante puede
crearse desde una matriz constante con la funcion VarArrayOf o a partir de la
nada mediante la llamada VarArra yCrea t e . Este es un fragment0 del codigo:
cds. Locate ( ' LastName;FlrstName' , VarArrayOf ( ['Cook',
'Kevin' ] ) , [I )
Por ultimo, se puede usar el mismo metodo para buscar un registro incluso
aunque solo se conozca el principio del campo que se esta buscando. Todo lo que
hay que hacer es aiiadir el indicador l o p a r t ialKe y a1 parametro o p t i o n s
(el tercero) de la llamada a Locate.

NOTA: Usar Locate tiene sentido cuando se trabaja con una tabla local,
pero no se adapta bien a las aplicaciones cliente/servidor. En un servidor
SQL, tknicas sirnilares por parte del cliente implican llevar en primer lu-
gar todos 10s datos a la aplicaci6n cliente (lo que es generalmente una rnala
idea) y buscar despues un registro especifico. Deberian localizarse 10s da-
tos mediante sentencias SQL restringidas. A h se puede usar Locate des-
puis de obtener un conjunto de datos limitado. Por ejemplo, se puede buscar
ciudad o zona dadas, con lo que se conseguira un conjunto de resultados de
tamailo reducido.

Deshacer y Savepoint
Cuando un usuario modifica 10s datos de un componente C l i e n t Da t a Se t ,
las actualizaciones se almacenan en una zona de memoria llamada D e l t a . El
motivo de hacer un seguimiento de 10s cambios del usuario en lugar de conservar
la tabla resultante se debe a1 mod0 en que se manejan las actualizaciones en una
arquitectura clientelservidor.
En este caso, el programa no tiene que enviar toda la tabla de vuelta al servi-
dor, sino solo una lista con 10s cambios del usuario (mediante sentencias SQL
especificas).
Ya que el componente C l i e n t D a t a S e t sigue la pista de 10s cambios, se
pueden rechazar esos cambios, eliminando entradas del delta. El componente po-
see un metodo U n d o L a s t C h a n g e especifico para ello. El parametro
F o l l o w c h a n g e de este metodo permite seguir la operacion deshacer (el con-
junto de datos del cliente se movera a1 registro que se ha recuperado mediante la
operacion deshacer). Veamos el codigo usado para conectar un boton Undo:
procedure TForml.ButtonUndoClick(Sender: TObject);
begin
.
cds UndoLastChange (True);
end :

Una ampliacion del soporte para deshacer modificaciones es la posibilidad de


guardar una especie de marcador de la posicion de registro del cambio (el estado
actual) y restaurarla mas tarde deshaciendo todos 10s cambios sucesivos. La pro-
piedad s a v e p o i n t puede utilizarse para guardar el numero de cambios en el
registro o para redefinir el registro segun una situation anterior. De todos modos;
solo podemos eliminar registros del registro de carnbio, no insertar cambios de
nuevo.
En otras palabras, la propiedad s a v e p o i n t se refiere a una posicion en un
registro, de manera que solo puede volverse a una posicion en la que habia menos
registros. Esta posicion de registro es un numero de cambios, asi que si se guarda
la posicion actual, se deshacen algunos cambios y despues se edita mas, no se
podra volver a la posicion marcada.
- - --- - . -- - - -- -
TRUCO: Delphi 7 ti'ieflexinanueva acci6n e&hdar proyectada sobre la
operacibn de 4fdacer &I ~ ~ i e ntta~~tae. Enttc akzw acciones nuevas
Activar y desactivar el registro
Realizar el seguimiento de 10s cambios tiene sentido si necesitamos enviar 10s
datos actualizados de nuevo a una base de datos servidor. En las aplicaciones
locales con datos almacenados en un archivo MyBase, mantener este registro
puede convertirse en algo inutil y consume memoria. Por dicha razon, podemos
desactivar el registro con la propiedad LogChanges. Lo malo es que asi tam-
bien detendremos las operaciones de deshacer.
Tambien podemos llamar a1 metodo MergeChangesLog para eliminar to-
das las ediciones actuales del registro de cambios y confirmar las modificaciones
realizadas hasta el momento. Tiene sentido si queremos mantener un registro de
modificaciones para deshacer durante una unica sesion y guardar despues el con-
junto de datos final sin guardar el registro de modificaciones.
- -- -

NOTA: El ejemplo M y ~ a s c 2desactiva el reiistra de modificaciones. Se


puede eliminar ese codigo y volver a activarlo para ver la diferencia de
tamafio entre el archivo CDS y el texto XML despuds de la edicion de 10s
I datos.

Uso de controles data-aware


Una vez que se han preparado 10s componentes apropiados para el acceso a
datos; se puede construir una interfaz de usuario para permitir una vista de usua-
rio de 10s datos y, en ultimo termino, su edicion. Delphi proporciona muchos
componentes que se pareccn a 10s controles habitudes pero son data-aware. Por
ejemplo, el componente DBEdit es similar a1 componente Edit y el componente
DBCheckBox corresponde a1 componente CheckBox. Podemos encontrar todos
estos componentes en la ficha Data Controls de la Component Palette de
Delphi. Todos estos componentes estan conectados a una fuente de datos que usa
la propiedad correspondiente, D a t a s o u r ce.Algunos de ellos se refieren a todo
el conjunto de datos, como 10s componentes DBGrid y DBNavigator, mientras
quc otros se refieren a un campo especifico de la fuente de datos, como indica la
propiedad D a t a F i e l d . Tras haber seleccionado la propiedad Da t a s o u r ce,
la propiedad de editor D a t a Field contendra una lista de valores disponibles.
Fijese en que 10s componentes sensibles a datos no estan en absoluto relacio-
nados con la tecnologia de acceso a datos, siempre que el componente de acceso a
datos herede de T D a t a S e t . Por eso, cualquier esfuerzo realizado en el desarro-
110 de la interfaz de usuario se conserva cuando se modifica la tecnologia de
acceso a datos. Aun asi, algunos de 10s componentes de busqueda y el uso exten-
dido del componente DBGrid (que muestra una gran cantidad de datos solo tienen
sentido cuando se trabaja con datos locales y, en general, deberia evitarse en
situaciones cliente/servidor.
Datos en una cuadricula
DBGrid es una cuadricula ( g n d ) capaz de mostrar una tabla completa instan-
taneamentc. Nos permite desplazarla y movernos por ella y tambien podemos
editar sus contenidos. Es una ampliacion de 10s otros controles de cuadricula de
Delphi.
Podemos personalizar DBGrid definiendo diversos indicadores de su propie-
dad opt ions y modificando su conjunto columns.La cuadricula permite a1
usuario moverse por 10s datos. usando barras de desplazamiento y rcalizando
todas las acciones principales. Un usuario puede editar 10s datos directamente,
insertar un nuevo registro en una posicion dada pulsando la tecla Insertar, adjun-
tar un nuevo registro al final yendo al ultimo registro y pulsando la tecla de cursor
hacia abajo, y borrar el registro actual pulsando Control-Supr.
La propiedad Columns es un conjunto en el que podemos seleccionar 10s
campos de la tabla que queremos ver en la cuadricula y configurar sus propieda-
des de columna y titulo (color, fuente, ancho, alineacion, titulo, etc.) para cada
campo. Algunas propiedades mas avanzadas, como But t o n S t y 1 e y
DropDownRows se pueden usar para ofrecer editores personalizados para las
celdas dc una cuadricula o una lista desplcgable de valores (indicados cn la pro-
piedad PickList de la columna).

DBNavigator y acciones sobre el conjunto


de datos
DBNavigator es una coleccion de botones utilizados para moverse por la base
dc datos y realizar acciones sobre la misma. Podemos desactivar algunos de 10s
botones del control DBNavigator, eliminando algunos de 10s elementos de la pro-
piedad de conjunto VisibleButtons.
Los botones realizan acciones basicas en el conjunto de datos conectado, por
lo que podemos sustituirlos facilmente por una barra de herramientas propia,
sobre todo si usamos un componente ActionList con las acciones de base de
datos predefinidas que nos ofrece Delphi. En ese caso, podemos conseguir todos
10s comportamientos estandar, per0 tambien se puede ver que 10s botones no se
habilitaran a no ser que se trate de una accion legitima. Las ventajas del uso de
acciones es que se pueden mostrar 10s botones con la disposicion que se prefiera,
mezclandolos con otros botones de la aplicacion y utilizar multiples controles de
cliente, como menus principales y de metodo abreviado.

TRUCO: Si utilizarnos las acciones estandar, podemos evitar conectarlas


a un componente Datasource especifico y las'acciones se aplicaran a1
~.ae aatos conecraao a1 control visual que riene en ese momenro
.---I--
conjunto 3 3 3 1 ' -1 . . . el .
foco de entrada. Asi, se puede usar una sola barra de herrarnientas para
1 vafiogconjwtone datoa mostrados por un fomu1ario.16 pue pucde resul- 1
( tiu muy conh~lsopara el osuario si no se t i a d en cuenta; 1

Controles data-aware de texto


Existen varios componentes de texto:
DBText: Muestra el contenido de un campo que el usuario no puede modi-
ficar. Es un control grafico Label data-aware. Puede resultar muy util,
per0 10s usuarios pueden confundir este control con las etiquetas simples
que indican el contenido de cada control basado en campos.
DBEdit: Permite al usuario editar un campo (cambiar el valor actual)
usando un control E d i t . Ocasionalmente se podria desear inhabilitar la
edicion y utilizar un DBEdit como si se tratara de un componente DBText,
per0 resaltar el hecho de que se trata del dato procedente de la base de
datos.
DBMemo: Permite a1 usuario vcr y modificar un campo de texto amplio,
almacenado en ultimo termino en un campo de memo o BLOB (binary
large objet). Se parece al componente Memo y tiene capacidades totales de
cdicion, pero todo el texto se presenta en una unica fuente.

Controles data-aware de lista


Para que el usuario pueda seleccionar un valor en una lista predefinida (lo cual
reduce 10s errores de entrada), podemos usar muchos componentes distintos.
DBLis tBox, DBComboBox y DBRadioGroup son sirnilares y ofrecen una
lista de cadenas en la propiedad Items, per0 presentan algunas diferencias:
DBListBox: Permite la seleccion de elementos predefinidos (seleccion ce-
rrada), pero no la entrada de texto y se puede usar para listar muchos
elementos. Por lo general, es mejor mostrar solo seis o siete elementos
aproximadamente, para evitar el uso de demasiado espacio en pantalla.
DBComboBox: Se puede usar tanto para una seleccion cerrada como para
permitir entradas del usuario. El estilo csDropDown del componente
DBComboBox permite que un usuario introduzca un nuevo valor, ademas
de seleccionar uno de 10s que hay disponibles. El componente usa tambih
una pequeiia zona del formulario porque la lista desplegable aparece solo
al solicitarla.
DBRadioGroup: Presenta botones de radio (lo que permite una unica se-
leccion), permite solo una seleccion cerrada y deberia de usarse solo para
un numero limitado de alternativas. Una caracteristica interesante es que
10s valores que muestre este componente pueden ser exactamente 10s mis-
mos que queramos insertar en la base de datos, pero tambien podemos
activar una especie de proyeccion. Los valores de la interfaz de usuario
(algunas cadenas descriptivas almacenadas en la propiedad I t e m s ) se
proyectaran a 10s valores correspondientes almacenados en la base de da-
tos (algunos codigos basados en caracteres o numeros listados en la pro-
piedad V a l u e s ) .
Por ejemplo, podemos proyectar algunos codigos numericos, que indiquen
departamentos sobre una serie de cadenas descriptivas:
object DBRadioGroupl: TDBRadioGroup
Caption = ' D e p a r t m e n t '
DataField = ' D e p a r t m e n t '
Datasource = DataSourcel
Items-Strings = (
'Sales'
' Accounting'
' Production'
' M a n a g e m e n t ')
Values .Strings = (
'1'
'2'
'3'
'4' )
end

D B C h e c k B o x es un componente ligeramente distinto. Solo se utiliza para


mostrar y alternar una opcion, correspondiente a un campo booleano. Se trata de
una lista limitada, porque solo tiene dos valores posibles, mas un estado indeter-
minado para 10s campos con valores nulos.
Podemos establecer cuales son 10s valores que hay que enviar de vuelta a la
base de datos configurando las propiedades V a l u e c h e c k e d y V a l u e U n -
c h e c k e d de este componente.

El ejemplo DbAware
El ejemplo DbAware resalta el uso de un control D B R a d i o G r o u p con 10s
parametros comentados en la seccion anterior y un control D B C h e c k B o x . Este
ejemplo no es mucho mas complejo que 10s anteriores, pero tiene un formulario
con controles data-aware orientados a campo, en lugar de una cuadricula que 10s
englobe todos. La figura 13.3 muestra el formulario del ejemplo en tiempo de
diseiio.
A1 igual que en el programa MyBase2, la aplicacion define su propia estructu-
ra de tabla, mediante la propiedad de conjunto F i e l d D e f s del ClientDataSet.
La tabla 13.1 proporciona un breve resumen de 10s campos definidos.
AddRandwnOaa
- --
I - - . .
* 41

\,
Q'

D d a S o w l -cdt -- -

I r Management
. - -.

Figura 13.3. Los controles data-aware del ejemplo DbAware en tlempo de diseiio.

Tabla 13.1. Los campos del conjunto de datos en el ejemplo DbAware.

LastName ft String
FirstName ftString
Department ftsmallint
Branch ftString
Senior ftBoolean
HireDate ftDate

El programa tiene algo de codigo para rellenar la tabla con valores aleatorios.
Este codigo es aburrido y no demasiado complejo, por lo que no vamos a comen-
tar 10s detalles, pero se puede analizar el codigo fuente de DbAware si se tiene
interes.

Uso de controles de busqueda


Si estraemos la lista de valores de otro conjunto de datos, en lugar de 10s
controles DBListBox y DBComboBox deberiamos usar 10s componentes es-
pecificos DBLoo kupList Box o DBLoo kupComboBox.Estos componentes
se usan cada vez que queremos seleccionar para un campo un valor que se corres-
ponda con un registro de otro con.junto de datos (iy no para escoger un registro
distinto!). Por ejemplo, si creamos un formulario estandar para aceptar pedidos,
el conjunto de datos de 10s pedidos generalmente tendra un campo en el que un
numero indicara el cliente que realizo el pedido. Lo mas normal es no traba.jar
directamente con el numero de cliente; la mayoria de 10s usuarios preferiran tra-
bajar con nombres de cliente. Sin embargo, en la base de datos, 10s nombres de
cliente se guardan en una tabla distinta, para evitar la duplicacion de 10s datos de
cliente por cada pedido realizado por el mismo cliente. Para solventar esta cues-
tion, con bases de datos locales o pequeiias tablas de busqueda, se puede usar un
control DBLookupComboBox. (Esta tecnica es dificil de adaptar bien a una
arquitectura cliente/servidor con tablas de busqueda grandes.)
El componente DBLookupComboBox se puede conectar a dos fuentes de datos
al mismo tiempo: una fuente que contenga 10s datos reales y una segunda que
contenga 10s datos que se muestran. Basicamente, hemos creado un formulario
estandar que usa el archivo o r d e r s . c d s de la carpeta de datos de muestra de
Delphi; el formulario i n c h ye varios controles DBEd i t .
Deberiamos eliminar el componente D B E d i t estandar conectado al numero
de cliente y sustituirlo por un componente DBLookupComboBox (y un compo-
nente DBText para entender lo que ocurre exactamente). El componente de bus-
queda (y DBText) esta conectado con el componente D a t a S o u r c e para el pedido
y con el campo CustNo. Para permitir que el componente de busqueda muestre la
informacion extraida de otro archivo ( c u s t o m e r . c d s ) , es necesario aiiadir
otro componente C l i e n t D a t a S e t que haga referencia a ese archivo, junto con
una nueva fuente de datos. Para que funcione el programa, es necesario configu-
rar algunas propiedades del componente DBLoo kupComboBoxl. Veamos una
lista de valores necesarios:
object DBLookupComboBoxl: TDBLookupComboBox
DataField = ' C u stNo'
DataSource = Datasourceorders
KeyField = ' Cus tNo'
ListField = 'Company;CustNo'
Listsource = DataSourceCustomer
DropDownWidth = 300
end

Las primeras dos propiedades establecen la conexion principal, como es habi-


tual. Las otras cuatro propiedades determinan el campo usado para la union de
10s datos (Key F i e l d ) , la informacion que se mostrara ( L i s t F i e l d ) y la fuen-
te secundaria ( L i s t S o u r c e ) . Ademas de escribir el nombre en un unico campo,
se pueden proporcionar multiples campos, como en el ejemplo. El primer campo
se muestra como texto en un cuadro combinado, pero si se establece un valor
grande para la propiedad D r opDownWid t h, la lista desplegable del cuadro
combinado incluira varias columnas de datos. El resultado aparece en la figura

qye contiene 10s datos de pedidos porno el oampacompgny, la lista desple-


g&le
.. mostrarh las empresas en &den a l f a b ~ i c oen hgar de hacerla por
@ h e r o de cliente del pedido. 4si se hjt hecho en el ejemplq.
Figura 13.4. El aspect0 del ejemplo CustLookup, con el DBLookComboBox mostrando
varios campos.

Controles graficos data-aware


Delphi incluye un control grafico data-aware, DBImage. Es una extension de
un componente Image que muestra una imagen guardada en un campo BLOB
(siempre que la base de datos utilice un formato grafico que soporte el componen-
te Image, como BMP y JPEG, este ultimo si se aiiade la unidad JPEG a la senten-
cia u s e s ) .
Una vez que se tiene una tabla que incluye un campo BLOB que almacena una
imagen con un formato grafico compatible, resulta trivial conectarlo a1 compo-
nente. Si, en lugar de eso, el formato grafico necesita una transformacion
personalizada para que se pueda mostrar, podria resultar mas sencillo usar un
componente Image estandar, que no sea sensible a 10s datos, y escribir codigo
para que la imagen se actualice cada vez que cambie el registro actual. Sin embar-
go, antes de poder comentar este tema, habria que saber mas sobre la clase
T D a t a S e t y las clases de campo de conjunto de datos.

El componente DataSet
En lugar de pasar a analizar las prestaciones de un conjunto de datos especifi-
cos, hemos preferido dedicar algo de espacio a una introduccion general a las
caracteristicas de la clase T D a t a S e t , que comparten todas las clases heredadas
de acceso a datos. El componente DataSet es bastante complejo, por lo tanto, no
enumeraremos todas sus capacidades sin0 solo sus elementos principales.
La idea tras este componente es la de proporcionar acceso a una serie de
registros que se leen desde alguna fuente de datos, se guardan en buffers internos
(por razones de rendimiento) y que el usuario podria modificar, con la posibilidad
de volver a escribir 10s cambios almacenandolos de forma permanente. Este enfo-
que es lo suficientemente genkrico como para que se pueda aplicar a tipos diferen-
tes de datos (incluso datos que no esten en bases de datos) per0 hay que seguir
unas ciertas normas:
En primer lugar, solo puede haber un registro activo en cada momento, por
lo que si tenemos que acceder a datos que estan en diversos registros,
debemos movernos a cada uno de ellos, leer 10s datos, desplazarnos de
nuevo y asi sucesivamente.
En segundo lugar, solo podemos editar el registro activo: no podemos mo-
dificar un conjunto de registros a1 mismo tiempo, como hacemos en las
bases de datos relacionales.
Podemos modificar 10s datos del buffer activo solo despues de haber decla-
rado explicitamente que deseamos hacerlo asi, dando la orden E d i t a1
conjunto de datos. Tambien podemos usar la orden I n s e r t para crear un
nuevo registro en blanco y cerrar ambas operaciones (de insercion o edi-
cion) con la orden PO s t .
Otros elementos interesantes de un conjunto de datos que analizaremos mas
adelante son su estado (y 10s eventos de cambio de estado), las posiciones de
navegacion y de registros, y el papel de 10s objetos de campo. Como resumen de
las prestaciones del componente Da t aSe t , en el listado 13.2 hemos incluido 10s
metodos publicos de la clase D a t a s e t (donde se ha editado y comentado el
codigo por claridad). No todos estos metodos se utilizan directamente de manera
habitual, per0 aun asi, hemos preferido mostrarlos todos.

Listado 13.2. La interfaz publica de la clase TDataSet (extracto).

TDataSet = class(TComponent, IProviderSupport)

public
/ / c r e a y d e s t r u y e , a b r e y cierra
c o n s t r u c t o r Create (AOwner: TComponent) ; override;
d e s t r u c t o r Destroy; override;
p r o c e d u r e Open;
p r o c e d u r e Close;
p r o p e r t y Beforeopen: TDataSetNotifyEvent r e a d FBeforeOpen
w r i t e FBeforeOpen;
p r o p e r t y Afteropen: TDataSetNotifyEvent r e a d FAfterOpen
w r i t e FAfterOpen;
p r o p e r t y Beforeclose: TDataSetNotifyEvent
r e a d FBeforeClose w r i t e FBeforeClose;
p r o p e r t y Afterclose: TDataSetNotifyEvent r e a d FAfterClose
w r i t e FAfterClose;

// i n f o r m a c i o n s o b r e el estado
f u n c t i o n IsEmpty: Boolean;
p r o p e r t y Active: Boolean r e a d GetActive w r i t e SetActive
d e f a u l t False;
p r o p e r t y State: TDataSetState r e a d FState;
function ActiveBuffer: PChar;
property IsUniDirectional: Boolean
read FIsUniDirectional write FIsUniDirectional default
False;
function Updatestatus: TUpdateStatus; virtual;
property Recordsize: Word read GetRecordSize;
property Objectview: Boolean read FObjectView write
SetOb jectView;
property Recordcount: Integer read GetRecordCount;
function IsSequenced: Boolean; virtual;
function IsLinkedTo(DataSource: TDataSource): Boolean;

// f u e n t e d e d a t o s
property Datasource: TDataSource read GetDataSource;
procedure DisableControls;
procedure EnableCont rols ;
function ControlsDisabled: Boolean;

/ / c a m p o s , corno b l o b s , d e t a l l e s , c a l c u l a d o s y o t r o s
function FieldByName(c0nst FieldName: string): TField;
function FindField (const FieldName: string) : TField;
procedure GetFieldList (List: TList; const FieldNames:
string) ;
procedure Get FieldNames (List: TStrings) ; virtual; // v i r t u a l
// d e s d e D e l p h i 7
property Fieldcount: Integer read GetFieldCount;
property FieldDefs: TFieldDefs read FFieldDefs write
SetFieldDef s;
property FieldDefList: TFieldDefList read FFieldDefList;
property Fields : TFields read FFields;
property FieldList: TFieldList read FFieldList;
property FieldValues[const FieldName: string]: Variant
read GetFieldValue write SetFieldValue; default;
property AggFields: TFields read FAggFields;
property DataSetField: TDataSetField
read FDataSetField write SetDataSetField;
property DefaultFields: Boolean read FDefaultFields;
procedure ClearFields;
function GetBlobFieldData(FieldN0: Integer;
var Buffer: TBlobByteData): Integer; virtual;
function CreateBlobStream(Fie1d: TField;
Mode: TBlobStreamMode) : TStream; virtual;
function GetFieldData(Fie1d: TField;
Buffer : Pointer) : Boolean; overload; virtual;
procedure GetDetailDataSets(List: TList); virtual;
procedure GetDetailLinkFields(MasterFields, DetailFields:
TList) ; virtual;
function GetFieldData(FieldN0: Integer;
Buffer: Pointer) : Boolean; overload; virtual;
function GetFieldData(Fie1d: TField; Buffer: Pointer;
NativeFormat: Boolean):
Boolean; overload; virtual;
property AutoCalcFields: Boolean
read FAutoCalcFields write FAutoCalcFields default True;
property OnCalcFields: TDataSetNotifyEvent
read FOnCalcFields write FOnCalcFields;

// p o s i c i d n , m o v i m i e n t o
procedure CheckBrowseMode;
procedure First;
procedure Last;
procedure Next;
procedure Prior;
function MoveBy(Distance: Integer): Integer;
property RecNo: Integer read GetRecNo write SetRecNo;
property Bof: Boolean read FBOF;
property Eof: Boolean read FEOF;
procedure CursorPosChanged;
property BeforeScroll: TDataSetNotifyEvent
read FBeforeScroll write FBeforeScroll;
property Afterscroll: TDataSetNotifyEvent
read FAfterScroll write FAfterScroll;

// marcadores
procedure FreeBookmark(Bookmark: TBookmark); virtual;
function GetBookmark: TBookmark; virtual;
function BookmarkValid(Bookmark: TBookmark): Boolean;
virtual ;
procedure GotoBookmark (Bookmark: TBookmark) ;
function CompareBookmarks(Bookmark1, Bookmark2: TBookmark):
Integer; virtual;
property Bookmark: TBookmarkStr read GetBookmarkStr write
SetBookmarkStr;

// b u s c a , l o c a l i z a
function FindFirst: Boolean;
function FindLast: Boolean;
function FindNext: Boolean;
function Findprior: Boolean;
property Found: Boolean read GetFound;
function Locate(const KeyFields: string; const KeyValues:
Variant;
Options: TLocateOptions): Boolean; virtual;
function Lookup(const KeyFields: string; const KeyValues:
Variant;
const ResultFields: string) : Variant; virtual;

// f i l t r a d o
property Filter: string read FFilterText write
SetFilterText;
property Filtered: Boolean read FFiltered write SetFiltered
default False;
property FilterOptions: TFilterOptions
read FFilterOptions write SetFilterOptions default [ I ;
property OnFilterRecord: TFilterRecordEvent
read FOnFilterRecord write SetOnFilterRecord:
// r e f r e s c a , a c t u a l i z a
procedure Refresh;
property BeforeRefresh: TDataSetNotifyEvent
read FBeforeRefresh write FBeforeRefresh;
property AfterRefresh: TDataSetNotifyEvent
read FAfterRefresh w r i t e FAfterRefresh;
procedure UpdateCursorPos;
procedure UpdateRecord;
function GetCurrentRecord(Buffer: PChar): Boolean; v i r t u a l ;
procedure Res ync (Mode: TResyncMode) ; v i r t u a l ;

// e d i t a , i n s e r t a , e n v i a y b o r r a
property CanModify: Boolean read GetCanModify;
property Modified: Boolean read modified;
procedure Append;
procedure Edit;
procedure Insert ;
procedure Cancel; v i r t u a l ;
procedure Delete;
procedure Post ; v i r t u a l ;
procedure AppendRecord (const Values : a r r a y of const) ;
procedure InsertRecord (const Values : a r r a y of const) ;
procedure SetFields(const Values: a r r a y of c o n s t ) ;

// e v e n t o s r e l a c i o n a d o s c o n e d i t a r , i n s e r t a r , e n v i a r y
// b o r r a r
property BeforeInsert: TDataSetNotifyEvent
read FBeforeInsert w r i t e FBeforeInsert;
property AfterInsert: TDataSetNotifyEvent
read FAfterInsert w r i t e FAfterInsert;
property BeforeEdit: TDataSetNotifyEvent read FBeforeEdit
w r i t e FBeforeEdit;
property AfterEdit: TDataSetNotifyEvent read FAfterEdit
w r i t e FAfterEdit;
property BeforePost: TDataSetNotifyEvent read FBeforePost
w r i t e FBeforePost;
property AfterPost: TDataSetNotifyEvent read FAfterPost
w r i t e FAfterPost;
property Beforecancel: TDataSetNotifyEvent
read FBeforeCancel w r i t e FBeforeCancel;
property Aftercancel: TDataSetNotifyEvent
read FAfterCancel w r i t e FAfterCancel;
property BeforeDelete: TDataSetNotifyEvent
read FBeforeDelete w r i t e FBeforeDelete;
property AfterDelete: TDataSetNotifyEvent
read FAfterDelete w r i t e FAfterDelete;
property OnDeleteError: TDataSetErrorEvent
read FOnDeleteError w r i t e FOnDeleteError;
property OnEditError: TDataSetErrorEvent
read FOnEditError w r i t e FOnEditError;
property OnNewRecord: TDataSetNotifyEvent
read FOnNewRecord w r i t e FOnNewRecord;
property OnPostError: TDataSetErrorEvent
r e a d FOnPostError w r i t e FOnPostError;

/ / soporte, utilidades
f u n c t i o n Translate (Src, Dest: PChar;
ToOem: Boolean): Integer; virtual;
property Designer: TDataSetDesigner r e a d FDesigner;
p r o p e r t y BlockReadSize: Integer r e a d FBlockReadSize write
SetBlockReadSize;
property SparseArrays: Boolean r e a d FSparseArrays write
SetSparseArrays;
end;

El estado de un Dataset
Cuando se trabaja sobre un conjunto de datos en Delphi, podemos trabajar en
distintos estados que nos indicara la propiedad especifica St ate, a la que pode-
mos dar diferentes valores:
dsBrowse: Indica que el conjunto de datos esta en un mod0 de navegacion
normal y se usa para ver 10s datos e inspeccionar 10s registros.
dsEdit: Indica que el conjunto de datos esta en mod0 de edicion. Un con-
junto de datos entra en este estado cuando el programa llama a1 metodo
Edit o cuando el Datasource tiene la propiedad A u t oEdi t configurada
como T r u e y el usuario comienza a editar un control data-aware, como
un DBGrid o DBEdit. Cuando se envia el registro que ha cambiado, el
conjunto de datos abandona el estado dsEdit.
dsInsert: Indica que se esta aiiadiendo un nuevo registro a1 conjunto de
datos. Esto podria ocurrir cuando se llama a 10s metodos Insert o
Append, moviendo la ultima linea de un componente DBGrid o usando la
orden correspondiente del componente DBNavigator.
dsInactive: Es el estado de un conjunto de datos cerrado.
dsCalcFields: Es el estado de un conjunto de datos mientras se esta reali-
zando el calculo de un campo, es decir, durante una llamada a un controla-
dor de eventos OnCalcFields.
dsNewValue, dsOldValue y dsCurValue: Son 10s estados de un conjunto
de datos cuando se esta actualizando la cache.
dsFilter: Es el estado de un conjunto de datos mientras se esta definiendo
un filtro, es decir durante una llamada a un controlador de eventos
OnFilterRecord.

En 10s ejemplos sencillos, las transiciones entre estos estados se controlan de


forma automatica, per0 es importante comprenderlos porque hay muchos eventos
que se refieren a las transiciones de estado. Por ejemplo, todo conjunto de datos
lanza eventos antes y despues de cualquier carnbio de estado. Cuando un progra-
ma solicita una operacion Edit, el componente lanza el evento Be foreEdi t
justo antes de pasar a1 mod0 de edicion (una operacion que podemos detener
creando una excepcion). Inmediatamente despues de pasar a1 mod0 de edicion, el
conjunto de datos recibe el evento AfterEdit.Despues de que el usuario haya
terminado de editar y solicite guardar 10s datos, ejecutando la orden Post, el
conjunto de datos produce un evento Before Post, que se puede usar para
verificar la entrada antes de enviar 10s datos a la base de datos, y un evento
After Post despues de que se haya finalizado satisfactoriamente la operacion.
Otra tecnica de seguimiento de carnbio de estado mas general implica gestio-
nar el evento Onstatechange del componente Datasource.Como ejemplo,
se puede mostrar el estado actual mediante un codigo como el siguiente:
p r o c e d u r e TForml.DataSourcelStateChange(Sender: T O b j e c t ) ;
var
strstatus: string;
begin
c a s e cds . S t a t e of
dsBrowse: s t r S t a t u s : = 'Browse' ;
d s E d i t : s t r S t a t u s := ' E d i t ' ;
dsInsert: s t r S t a t u s := ' I n s e r t ' ;
else
s t r s t a t u s := 'Other s t a t e ' ;
end;
S t a t u s B a r . Panels [ 0 ] .Text : = s t r S t a t u s ;
end:

Los campos de un conjunto de datos


Un conjunto de datos solo tiene un registro que sea el activo o actual. El
registro se almacena en un buffer y podemos trabajar en el mediante algunos
metodos generales, per0 para acceder a 10s datos del registro, hay que usar 10s
objetos de campo del conjunto de datos. Esto explica por que 10s componentes de
campo (tecnicamente instancias de una clase derivada de la clase TField) po-
seen una funcion basica en todas las aplicaciones de bases de datos Delphi. Los
controles data-aware estan conectados directamente con estos objetos de campo,
que corresponden a campos de la base de datos.
De manera predeterminada, Delphi crea automaticamente 10s componentes
T F ie 1d en tiempo de ejecucion, cada vez que el programa abre un componente
de conjunto de datos. Esto se hace tras leer 10s metadatos asociados con la tabla o
consulta a la que se refiera el conjunto de datos.
Dichos componentes de campo se almacenan en la propiedad de matriz Fields
del conjunto de datos. Podemos acceder a dicho valores por numero (accediendo
directamente a la matriz) o por nombre (usando el metodo FieldByName). Se
puede usar cada campo para leer o modificar 10s datos del registro actual, utili-
zando su propiedad Value o propiedades de tipos especificos, como A s Da t e ,
Asstring, AsInteger, etc.:
var
strName: string;
begin
strName : = cds. Fields [0].Asstring
strName : = cds.FieldByname('LastName') .Asstring

Value es una propiedad de tipo variante, por lo que resulta en cierto mod0
mas eficiente usar propiedades de acceso especificas de tipo. El componente de
conjunto de datos tiene tambien una propiedad de metodo abreviado para acceder
a1 valor de tipo variante de un campo, la propiedad predefinida FieldValues.
A1 ser una propiedad predefinida significa que podemos omitirla en el codigo
aplicando directamente 10s corchetes a1 conjunto de datos:
strName : = c d s .Fieldvalues [ ' L a s t N a m e '] ;
strName : = cds [ ' L a s t N a m e l] ;

Crear 10s componentes de campo cada vez que se abre un conjunto de datos es
solo un comportamiento predefinido. Como alternativa, podemos crear 10s com-
ponentes de campo en tiempo de disefio, usando el editor Fields (para ver este
editor en funcionamiento, hay que hacer doble clic sobre un conjunto de datos, o
activar su menu local o el de la vista Object TreeView y escoger la opcion
Fields Editor). Despues de crear un campo para la columna LastName de una
tabla, por ejemplo, podemos referirnos a su valor aplicando uno de 10s metodos
AsXxx a1 objeto de campo adecuado:

Ademas de ser utilizado para acceder a1 valor de un campo, cada objeto de


campo tiene tambien propiedades de visualizacion y edicion de su valor, como
rangos de valores, mascaras de edicion, formatos de presentacion, restricciones y
muchas otras. Por supuesto, dichas propiedades dependen del tip0 de campo, es
decir, de la clase especifica del objeto de campo. Si creamos campos permanentes
podemos definir algunas propiedades en tiempo de diseiio, en lugar de escribir
codigo en tiempo de ejecucion, tal vez en el evento A f teropen del conjunto de
datos.

NOTA:Aunque el editor Fields es similar a 10s editores de las colecciones


utiliaadas por Delphi, 10s campos no forrnan parte de una coleccih Son
canpci6&$ creados en tiempo de diseiio, enumerados en la seccih publi-
cada de La blase de fopnulario y disponibles en el cuadro combinado desple-
gable en la parte superior &! Dbj'eet. Inspector.

A1 abrir el editor Fields de un conjunto de datos, aparece vacio. Hay que


activar el menu local de este editor o el del pseudonodo Fields en la vista Object
TreeView para acceder a sus capacidades. La operacion mas sencilla consiste en
seleccionar la orden A d d , que permite aiiadir cualquier otro campo del conjunto
de datos a la lista de campos. La figura 13.5 muestra el cuadro de dialogo Add
Fields, que lista todos 10s campos disponibles en una tabla. Estos son 10s campos
de tabla de base de datos que aun no estan presentes en la lista de campos en el
editor.

Name

Figura 13.5. El editor Fields con el cuadro de dialogo Add Fields.

La orden Define del editor Fields permite definir un nuevo campo calcula-
do, un campo de busqueda o un campo con un tipo modificado. En este cuadro de
dialogo, se puede escribir un nombre de campo descriptivo, que podria incluir
espacios en blanco.
Delphi genera un nombre interno (el nombre del componente de campo) que
ademas se puede personalizar. A continuacion, hay que seleccionar un tip0 de
datos para el campo. Si este se trata de un campo calculado o un campo de
busqueda y no solo una copia de un campo redefinido para usar un nuevo tipo de
datos, simplemente hay que activar el boton de radio apropiado.

NOTA: Un componente T F i e l d tiene una propiedad Name y una propie-


dad F i e l d N a m e . La propiedad Name es el nombre habitual del compo-
nente. La propiedad FieldName es el nombre de la colurnna de la tabla en
la base de-daios o el nombre que definamos para el campo calculado. Puede
ser mas descriptivo que Name y permite espacios en blanco. La propiedad

la propiedad D i s p l a y L a b e l , pero este nombre de carnpo puede cam-


biarse por cualquier text0 apropiado. Se usa, entre otras cosas, para buscar
un campo en el m a w 0 r l e-,
-- -1 --l*-J- rrl 2- ...-- > - 1- - I - - -
l a a y m a r n e- ae m - ,.._ _.
la clase xuar;aseL
L L 3-
y cuanao
se usa la notacih de matriz.
Todos 10s campos que aiiadimos o definimos se incluyen en el editor Fields y
lo pueden usar 10s controles data-aware o aparecer en una cuadricula de base de
datos. Si un campo del conjunto de datos fisico no esta en esta lista, no se podra
acceder a el. Cuando utilizamos el editor Fields, Delphi aiiade la declaration de
10s campos disponibles a la clase del formulario como componentes nuevos (de un
mod0 muy parecido a como aiiade el menu Designer 10s componentes T M e n u I t e m
a1 formulario). Los componentes de la clase T F i e l d , o mas concretamente sus
subclascs, son campos del formulario y podemos referirnos directamente a estos
componentes en el codigo del programa para cambiar sus propiedades en tiempo
dc ejecucion o para obtener o establecer su valor.
En el editor Fields, tambien podemos arrastrar 10s campos para modificar su
orden. Resulta especialmente importante ordenar 10s campos correctamente cuan-
do definimos una cuadricula, puesto sus columnas se ordenan utilizando ese or-
den.

TRUCO: Tambien se pueden arrastrar 10s campos desde el editor a1 for-


mulario para dejar que el IDE Cree 10s componentes visuales automiitica-
mcnte Sc tratn rlc Nina ~aracterictieamiiv nrictica niw n11~Ae
ahnrrar miiehn

Uso de objetos de campo


Antes de analizar un ejemplo, vamos a comentar el uso de la clase T F i e l d .
No hay que subestimar la importancia de este componente: aunque lo mas normal
sea utilizarlo de manera interna, su papel en las aplicaciones de bases de datos es
fundamental. Como ya hemos comentado: aunque no definamos objetos especifi-
cos de ese tipo, siempre podemos acceder a 10s campos de una tabla o de una
consulta usando su propiedad de matriz F i e l d s , la propiedad indexada
F i e l d v a l u e s o el metodo F i e l d B y N a m e . Tanto la propiedad F i e l d s como
la funcion F i e l d B y N a m e devuelven un objeto del tipo T F i e l d , de mod0 que a
veces tenemos que usar el operador a s para realizar una conversion de tipos
posterior del resultado a su tipo real (corno T F l o a t F i e l d o T D a t e F i e l d )
antes de acceder a propiedades concretas de dichas subclases.
El ejemplo FieldAcc tiene un formulario con tres botones de velocidad en el
panel de la barra de herramientas, que acceden a varias propiedades de campo en
tiempo de ejecucion. El primer boton cambia el formato de la columna "Population"
de la cuadricula. Para ello, hemos de acceder a la propiedad D i s p l a y F o r m a t ,
una propiedad especifica de la clase T F l o a t F i e l d :
procedure TForm2.SpeedButtonlClick(Sender: TObject);
begin
.
(cds FieldByName ( ' Population') as
TFloatField) .DisplayFormat : = ' ###,###,###I ;
end;

Cuando definimos propiedades de campo relacionadas con entradas o salidas


de datos, 10s cambios se aplican a cada registro de la tabla. En cambio, cuando
definimos propiedades relacionadas con el valor del campo siempre nos referimos
unicamente a1 registro actual. Por ejemplo, podemos obtener la poblacion
(population) del pais actual en un cuadro de mensaje escribiendo el siguiente
codigo:
p r o c e d u r e TForm2.SpeedButton2Click(Sender: T O b j e c t ) ;
begin
ShowMessage ( s t r i n g ( c d s [ 'Name'] ) + ' : ' + s t r i n g (cds
[ ' Population' 1 ) ) ;
end;

Cuando accedemos a1 valor de un campo, podemos usar una serie de propieda-


des AS para controlar el valor de campo actual usando un tip0 de datos especifico
(si este esta disponible, si no, se crea una excepcion):
AsBoolean: Boolean;
AsDateTime: TDateTime;
AsFloat : Double;
A s Integer : LongInt;
A s s t r i n g : string;
Asvariant: Variant;

Estas propiedades se pueden usar para leer o cambiar el valor del campo. Para
cambiar el valor de un campo, el conjunto de datos habra de estar en mod0 de
edicion. Otra alternativa a1 uso de las propiedades As, es acceder a1 valor de un
campo usando su propiedad value, que se define como una variante.
La mayoria de las demas propiedades del componente TField, tales como
Alignment, DisplayLabel, Displaywidth y Visible, reflejan ele-
mentos de la interfaz de usuario del campo y las utilizan 10s distintos controles
data-aware, sobre todo DBGrid.
En el ejemplo FieldAcc, haciendo clic sobre el tercer boton de velocidad cam-
bia la alineacion de cada campo:
p r o c e d u r e TForm2.SpeedButton3Click(Sender: TObject);
var
I: Integer;
begin
f o r I : = 0 t o cds.FieldCount - 1 d o
cds. Fields [I] .Alignment : = tacenter;
end;

Esto afecta a la salida de la DBGrid y del control DBEdit aiiadido a la


barra de herramientas, que muestra el nombre del pais (country), como muestra la
figura 13.6.
Formal
t I . NdmO
.
ShowPop Ccnln
- I .. Cuba
- . . I.
u 4
I- I
b H

-
-
+
-
-
-
b
-
-
-
-
-
-
-
-
Un&d Slates of Arnenca NorthArnexa 9363130 249M0 000
A
Figura 13.6. El aspect0 del ejemplo F~eldAccdespues de haber pulsado 10s botones
Center y Format

Una jerarquia de clases de campo


Existen varios tipos de clases de campo en l a VCL. Delphi utiliza
automaticamente una de ellas dependiendo de la definici6n de datos que haya en la
base de datos, cuando abrimos una tabla en tiempo de ejecucion o cuando usamos
el editor Fields en tiempo de disefio. La tabla 13.2 muestra la lista completa de
subclases de la clase T F i e l d

Tabla 13.2. Las subclases de TField.

TADTField TObj ectField Un carnpo ADT (Abstract Data


Type, Tipo de Datos Abstracto),
correspondiente a un campo de
objeto en una base d e datos
relacional de objetos.
TAggregateField TField Un camp0 agregado representa
un agregado rnantenido. Se usa
en el componente ClientDataSet.
TArrayField T O b j ect Field Una rnatriz d e objetos en una
base de datos relacional.
TAutoIncField TIntegerField Un ndrnero enter0 positivo conec-
tad0 con un campo autoincre-
mental de una tabla Paradox (un
campo especial al que se asigna
automaticamente un valor diferen-
te para cada registro). Fijese en
q u e 10s campos A u t o l n c de
Paradox no siempre funcionan
correctamente.
TNumericField Numeros reales con un numero
fijo de digitos despues de una
coma decimal.
TField Normalmente no utilizado de for-
ma directa. Esta es la clase ba-
sics de las dos clases siguientes.
TField Datos binarios y sin limite de ta-
mafio ( B L O B significa objeto
binario grande). El limite maxi-
mo teorico son 2GB.
TField Un valor booleano.
TBinaryField Datos arbitrarios con un tamafio
amplio per0 fijo (hasta 64 KB de
caracteres).
TFloatField Valores monetarios, con el mis-
mo rango que el tipo de datos
Real.
Un objeto correspondiente a una
tabla separada en una base de
datos relational de objetos.
TDateField TDateTimeField Un valor de fecha.
TDateTimeField TField Un valor de fecha y hora.
TFloatField TNumericField Numeros de coma decimal (8
bytes).
TNumericField (Nuevo tip0 de campo en Delphi
6). Verdadero decimal en codigo
binario (binary-coded decimal,
B C D ) , en oposicion al tipo
TBCDField ya existente, que con-
vertia valores BCD al tipo de
moneda. Este tip0 de campo se
usa solo automaticamente en
conjuntos de datos dbExpress.
Grafico de longitud arbitraria.
TStringField Un carnpo que representa un
ldentificador Global Unico de
COM, parte del soporte ADO.
Un carnpo que representa punte-
ros a las interfaces I D i s p a t c h
de COM, parte del soporte ADO.
TIntegerField TNumericField Nllrneros enteros en el rango de
enteros largos (long integer) (32
bits).
TField Norrnalmente no se usa directa-
rnente. Es la clase basica de 10s
carnpos que contienen punteros
a interfaces (IUnknown) corno
datos.
TLargeIntField TIntegerField Enteros rnuy arnplios (64 bit).
TMemoField TBlobField Texto de longitud arbitraria.
TNumericField TField Normalrnente no se usa directa-
rnente. Es la clase basica d e
todas las clases de carnpo nume-
ricas.
TObjectField TField Normalmente no se usa directa-
mente. La clase basica de 10s
campos que ofrecen soporte a
bases de datos relacionales de
objetos.
TObjectField Un punter0 a un objeto en una
base relational de objetos.
TIntegerField Nhmeros enteros en el rango de
enteros (integer) (16 bits).
TField (Nuevo tip0 de campo en Delphi
6). Soporta la representacion de
fecha y hora utilizada en 10s
controladores dbExpress.
TStringField TField Datos de texto de una longitud fija
(hasta 81 92 bytes).
TDateTimeField Un valor de hora.
TBytesField Datos arbitrarios, hasta caracte-
res de 64 KB. Muy similar a la
clase bdsica TBytesField.
TVariantField TField Un carnpo que representa un tip0
de datos variante, parte del so-
porte ADO.
TWideStringField TStringField Un campo que representa una ca-
dena Unicode (16 bits por carac-
ter).
TWordField TIntegerField Enteros positivos en el rang0 de
palabras o enteros sin signo (16
bits).

La disponibilidad de un tip0 de campo concreto y la correspondencia con la


definition de datos dependen de la base de datos que se use, sobre todo, con
respecto a 10s nuevos tipos de datos que ofrecen soporte para bases de datos
relacionales de objetos.

Adicion de un campo calculado


Ahora que hemos visto 10s objetos T F i e l d y un ejemplo de su uso en tiempo
de ejecucion, construiremos un ejemplo basado en la declaracion de objetos de
campo en tiempo de diseiio, utilizando el editor Fields y aiiadiendo un campo
calculado. En el conjunto de datos de muestra c o u n t r y . c d s , estan disponibles
tanto la poblacion como el area de cada pais; se pueden usar estos datos para
calcular la densidad de poblacion. Para crear el nuevo ejemplo, llamado Calc,
hay que seguir estos pasos:
1. Aiiadir un componente ClientDataSet a un formulario.
2. Abrir el editor Fields. En este editor, hay que hacer clic con el boton
derecho del raton para escoger la orden Add F i e l d y seleccionar algu-
nos de 10s campos. (Nosotros incluiremos todos.)
3 . Ahora, hay que seleccionar la orden N e w F i e l d y escribir un nombre y
tipo de datos adecuado (Flota, para un T F l o a t F i e l d ) para el nuevo
campo calculado, como muestra la figura 13.7.
-

ADVERTENCIA: Resulta obvio que a1 crear algunos componentes de


campo en tiempo de diseiio utilizando el editor Fields, 10s campos que
omitamos no mdrh un objeto correspondiente. Ni siquiera e s t a h dispo-
nibtes en tierepa de ejecucih, con Fields y FieldByNarne. Cuando un
programa abre una tabla en tiempo de ejecucibn, si no hay compontntes de
c a m p en Gempa & ~~, Delplsi creaobjetos de camp mrespondi~tes
a las defmiciones de tabla. Sin embargo, si hay algunos campos en tiempo
de disefio, Delphi usa esos campos sin aiiadir ninguno adicional.

Figura 13.7. La definicion de un campo calculado en el ejemplo Calc.

Por supuesto, tambien tenemos que proporcionar un mod0 de calculo para el


nucvo campo. Para ello, usamos el evento O n C a l c F i e l d s del componente
C 1ient Dat aSe t , que tiene el siguiente codigo (a1 menos en una primera version):

procedure TForm2.cdsCalcFields(DataSet: TDataSet) ;


begin
cdsPopulationDensity.Value : = cdsPopulation.Va1ue /
cdsArea.Value;
end;

NOTA: Los campos calculados se calculan para cada registro y se vuelven


a calcular cada vez que el registro se carga en un buffer interno, recurrien-
3 I .
ao a1 evenro - . - .. -
u n c a l c r ' l e las una y orra vez. rno r eso,
I 1 3>
el conrrolaaor ae
este evento deberia ser muy ripido en su ejecucion y no puede alterar el
estado del conjunto de datos, accediendo a registros diferentes. El compo-
nente C l i e n t D a t aSe t -proporciona
- una version m k eficiente en tiempo
(pero menos en memoria) de un campo calculado con 10s campos calcula-
dos intemamente: estos campos se calculan una sola vez (cuando se cargan)
y el resultado se almacena en memoria para peticiones futuras.

Pero eso no es todo. Si introducimos un nuevo registro y no definimos el valor


de la poblacion ni la superficie, o si accidentalmente definimos la superficie como
cero, la division creara una excepcion y habra problemas para continuar usando
el programa.
Para evitarlo, podriamos haber controlado cualquier excepcion de la expresion
de division y fijar sencillamente el valor resultante como cero:
try
cdsPopulationDensity.Value : = c d s ~ o p u l a t i o n . V a l u e /
cdsArea.Value;
except
on Exception do
cdsPopulationDensity.Value : = 0;
end:

Se puede hacer aun mejor: se puede comprobar que se encuentre definido el


valor del area (si no es nulo) y que no sea cero. Es mejor evitar el uso de excepcio-
nes cuando se pueden anticipar las posibles condiciones de error:
i f n o t cdsArea. IsNull and (cdsArea.Value <> 0) then
cdsPopulationDensity.Value : = cdsPopulation.Value /
cdsArea.Value
else
cdsPopulationDensity.Value : = 0;

El codigo del metodo cdscalc Fields (en cada una de las tres versiones)
accede directamente a algunos campos. Se puede hacer asi porque se ha usado el
editor Fields y, automaticamente, se ha encargado de la creacion de las declara-
ciones de campo correspondientes, como se puede ver en este extract0 de la decla-
ration de interfaz del formulario:

type
TCalcForm = c l a s s (TForm)
cds: TClientDataSet;
cdsPopulationDensity: TFloatField;
cdsArea: TFloatField;
cdspopulation: TFloatField;
cdsName: TStringField;
cdscapital: TStringField;
cdscontinent: TStringField;
procedure cdsCalcFields (Dataset: TDataSet) ;

Cada vez que aiiadimos o eliminamos campos en el editor Fields, podemos ver
el efecto inmediato en la cuadricula del formulario (a no ser que la cuadricula
tenga definidos sus propios objetos de columna, en cuyo caso normalmente no se
vera ningun cambio) .
Por supuesto, en tiempo de diseiio no se veran 10s valores de un campo calcu-
lado; solo se encuentran disponibles en tiempo de ejecucion, porque son el resul-
tad0 de la ejecucion del codigo Delphi compilado.
Dado que hemos definido algunos componentes para 10s campos, podemos
usarlos para personalizar algunos elementos visuales de la cuadricula. Por ejem-
plo, para definir un formato de presentacion que aiiada un punto para separar 10s
miles, podemos usar el Object Inspector para cambiar l a propiedad
DisplayFormat de algunos componentes de campo a # # # , # # # , # # # . El
efecto de este cambio en la cuadricula es inmediato en tiempo de diseiio.
NOTA: El formato de presentacion mencionado utiliza la configuration
intemacional de Windows para dar formato a la salida. Cuando Delphi
-- -
traauce
A _ _ 3
el valor .numerlco
-1......
ae este campo_ a- rexto, Ira- coma
l-: 3- -_A- - -.-I _ _ _ J _ . . _
en ta caaena ae
A_--*_ 3-

formato se sustituye por el caracter Thousandse p a r a t o r apropiado.


.. .- .
aistintas contiguraciones
-
,. . . . . -.
Por este motivo, el aspect0 del programa se adapitara automaticamente a
intemacionales. c n 10sordenadores con una con-
fig,uracion angloamericana, la coma seguira siendo una coma, !{ en una
co1nfiguracion en espaiiol, se sustituira por un punto.

Despues de trabajar con 10s componentes de tabla y 10s campos, hemos perso-
nalizado el componente DBGrid usando el editor de su propiedad C o l u m n s .
Hemos configurado la columna Popzrlation Density (densidad de poblacion) como
de solo lcctura y su propiedad But tonstyle como cbsEllipsis, para ofrecer
un editor personalizado. Cuando definimos este valor, aparece un pequeiio boton
con tres puntos si el usuario intenta editar la celda de la cuadricula. Al pulsar el
boton sc invoca a1 evento OnEditButtonClick de la DBGrid:
procedure T C a l c F o r m . D B G r i d l E d i t B u t t o n C l i c k ( S e n d e r : TObject);
begin
MessageDlg (Format (
' T h e p o p u l a t i o n d e n s i t y ( 8 . 2 n ) #I3 +
' i s t h e P o p u l a t i o n ( % . O n ) '#I3 +
' d i v i d e d by t h e A r e a (%.On). '#13#13 +
' E d i t these two f i e l d s t o c h a n g e i t . I ,
[cdsPopulationDensity.AsFloat,
cdsPopulation.AsFloat,
cdsArea.AsFloat] ) ,
mtInf ormation, [mbOK] , 0 ) ;
end;

En rcalidad, no hemos proporcionado un editor real, sino un mensaje que des-


cribe la situation, como se ve en la figura 13.8, que muestra 10s valores de 10s
campos calculados. Para crear un editor, podriamos crear un formulario secunda-
rio para manejar las entradas de datos especiales.

A q n 1 ,m Buenar P r ~ r Sauth Ameuca 32 300 003 2 777 815 1163 .


SoulhAmerce
SarlhAmnlcs
Nolthh~ca
SouthAmerlce
Soulh Amctea
NmlhArnnca
Solnh America
Narthhaim

Figura 13.8. El resultado del ejemplo Calc. Fijese en la columna calculada de


densidad de poblacion (Population Density) y e n el boton de tres puntos que aparece
cuando se edita.
Campos de busqueda
Como alternativa a colocar un componente D B L o o k u p C o m b o B o x en un for-
mulario (algo que ya hemos comentado antes), tambien podemos definir un cam-
po de busqueda, que puede mostrarse con una lista de busqueda desplegable dentro
de un componente D B G r i d . Hemos visto que para aiiadir una selection fija a una
D B G r i d, podemos sencillamente editar la subpropiedad P i c k L i s t de la pro-
piedad c o 1u m n s .
Para personalizar la cuadricula con una busqueda en directo, en cambio, tene-
mos que definir un campo de busqueda utilizando el editor Fields.
Como ejemplo, hemos construido el programa FieldLookup, que tiene una
cuadricula en la que aparecen pedidos con un campo de busqueda para mostrar el
nombre del empleado que anoto el pedido, en lugar del numero de codigo de dicho
empleado. Para ello, hemos aiiadido a1 modulo de datos un componente
C l i e n t D a t a S e t que se refiere a1 conjunto de datos e m p l o y e e . c d s . A con-
tinuacion, hemos abierto el editor Fields para el conjunto de datos o r d e r s . c d s
y hemos aiiadido todos 10s campos.
Hemos seleccionado el campo E m p N o y hemos definido su propiedad V i s i -
b l e como F a 1 s e para eliminarla de la cuadricula (no podemos eliminarla por
completo, porque se usa para crear la referencia cruzada a1 campo correspondien-
te del conjunto de datos de empleados).
Ahora, hay que definir el campo de busqueda. Si se han seguido 10s pasos
anteriores, se puede utilizar el editor Fields del conjunto de datos de pedidos
( o r d e r s .c d s ) y seleccionar la orden N e w F i e l d , con lo que aparecera el
cuadro de dialog0 New Field. Los valores que especificamos aqui afectaran a las
propiedades de un nuevo T F i e l d aiiadido a la tabla, tal como muestra la des-
cripcion DFM del campo:
object cds2Employee: TStringField
FieldKind = f kLookup
FieldName = ' E r p l o y e e '
LookupDataSet = cds2
LookupKeyFields = ' E r p N o '
LookupResultField = ' L a s t N a r n e '
KeyFields = ' E r p N o '
Size = 3 0
Lookup = True
end

Esto es todo lo que se necesita para que la lista desplegable funcione (vease la
figura 13.9) y tambien para ver el valor del campo de la referencia cruzada en
tiempo de diseiio. Fijese en que no es necesario personalizar la propiedad C o l u m n s
de la cuadricula porque se usa el boton desplegable y el valor de siete filas viene
de manera predefinida. Eso no significa que no podamos usar esta propiedad para
personalizar aun mas estos y otros elementos visuales de la cuadricula a nuestro
gusto.
Figura 13.9. El resultado del ejemplo FieldLookup, con la lista desplegable de la
cuadricula que muestra valores tornados de la tabla de otro conjunto de datos.

Estc programa tiene otra caracteristica especifica. Los dos componentes


C l i e n t D a t a S e t y 10s dos componentes D a t a s o u r c e no se han colocado
sobre un formulario sino en un contenedor especial para componentes no visuales
llamado modulo de datos. Se puedc conseguir un modulo de datos mediante la
opcion de menu File>New de Delphi. Despues de afiadirle componentes, se pue-
den vincular con controles de otros formularios mediante la orden F i l e N s e Unit.
-- -- - - - -

Un m6dulo de datos para 10s componentes de acceso


a datos
Para crear una aplicacion de bases de datos en Delphi, se puede colocar 10s
componentes de acceso a datos y 10s componentes data-aware en un formu-
lario. Este enfoque es practico para un programa sencillo, pero tener la
interfaz de usuario y el modelo de datos y de acceso a datos en una unica
. I I , I . 1 .. uelpnt
I
uniaaa \generalmenre granae) no es una Duena iaea. r o r esre moavo, - . a . . . I II

im~plementael concept0 de un modulo de datos: un contenedor de compo-


nentes no visuales.
. .. - .-. - - .- - . .
En tiemvo de diseiio, un modulo de datos es varecido a un tormulano,-vero .
c:n tiempo de ejecucion existe solo en memoria. La clase TDataModule
(jeriva directamente de TComponent, asi que no esta relacionada con el
.^-^^-*-
C U I I G G ~ L VUG
-I- x x 7 : - 1 - - - .
w IIIUUWS ^ -1- -.^-L^-^
UG UIM V G I I L ~ ~ (y
f-. -.. ^^--^-l^L--^-L^ ---L-Ll-
I I ~GS C ; U I I I ~ I G L S L I ~ I G I I~L G
^-L-^
U I L ~ U IGllLrt;
G

(jistintos sistemas operativos). Al contrario que un formulario, un Imodulo


(je datos tiene simplemente unas cuantas lxopiedades y eventos. Po r eso,
....I. I . . . I
results utli pensar en 10s moaulos ae aaros como en conteneaores de corn-
9 I . I

ponentes y metodos. Al igual que un fonnulario o un marco, un modulo de


datos tiene un diseiiador. Delphi crea una unidad especifica para la defini-
cion de la clase de modulo de datos y un archivo de definicion de formulario
que lista sus componentes y propiedades.
x x i s t e n varios motivos Dara usarm6dulos dejatos. El mis simde gs aue
I~ermitencompartir componentes de acceso a datos entre varios formula-
1-ios. Esta tecnica funciona junto con el enlace de formularios visuates (la
1 1 - 1 1- -----I--
G a y a u u i r o UG acc;r;ur;I
^ ^^--^-^-*^^
a GUIII~UIIGIILG~
-1-
UG ULIU
----
^*-^ r I--:-
LUIIIIUI~IIU
^ -LA-I-
u IIIUUUIU
-I- -I^*^-
us; uarus
en tiempo de diseiio mediante la orden FiIeNse Unit). El segundo motivo
es que 10s modulos de datos separan 10s datos de la interfaz de usuario,
mejorando la estructura de una aplicacion. Los m6dulos de datos en Delphi
tienen incluso versiones especificas para aplicaciones multicapa (modulos
de datos remotos) y aplicaciones HTTP de servidor (modulos de datos Web).

Control de 10s valores nulos con eventos


de campo
Ademas de otras propiedades mas interesantes, 10s objetos de campo pueden
tener varios eventos clave. El evento OnValidate se puede utilizar para pro-
porcionar una validacion ampliada del valor de un campo y deberiamos de usarlo
siempre que sea necesaria una regla compleja que no se pueda expresar mediante
10s rangos y restricciones ofrecidos por el campo. Este evento se desencadena
antes de que 10s datos se escriban en el buffer del registro, mientras que el evento
OnChange ocurre poco despues de que se hayan escrito 10s datos.
Existen otros dos eventos, OnGetText y OnSetText, que podemos usar
para personalizar la salida de un campo. Estos dos eventos resultan extremada-
mente potentes: permiten usar controlcsdala-aware incluso cuando la representa-
cion de un campo que queremos mostrar es distinta de la que Delphi proporciona
de manera predefinida.
Un ejemplo del uso de dichos eventos es el control del valor nulo. En servido-
res SQL, guardar un valor vacio o un valor nulo para un campo son operaciones
independientes entre si. La ultima suele a ser mas corrects, pero Delphi utiliza por
defccto valorcs vacios y muestra la misma salida para un campo vacio o nulo.
Aunque esto pueda resultar util por lo general para cadenas y numeros, se hacc
realmente importante en el caso de las fechas, puesto que resulta dificil establecer
un valor predefinido razonable, y si el usuario dcja en blanco el campo podriamos
tener una entrada no valida.
El programa NullDates muestra un texto especifico para fechas que tengan un
valor nulo y vacia el campo (definiendolo con10 de valor nulo) cuando el usuario
utiliza una cadena en blanco en la entrada. Veamos el codigo pertinente de 10s dos
controladores de eventos del campo:
procedure TForml.cdsShipDateGetText(Sender: TField;
var Text : String; DisplayText : Boolean) ;
begin
if Sender.IsNul1 then
Text : = ' < u n d e f i n e d > '
else
Text : = Sender .Asstring;
end;

procedure TForml.cdsShipDateSetText(Sender: TField; const T e x t :


String) ;
begin
if Text = " then
Sender.Clear
else
Sender.AsString : = Text;
end;

La figura 13.10 muestra un ejemplo de la salida del programa, con valores no


definidos (o nulos) para algunas fechas de envio

l 4 4 w U + - A p 01brNo ICU~NO
l~ak~ate I~hpDate IE~~NO
U1298CN2315 9/1/1995 91111995 EmpU 0011
OrdnNo
U1300 CN 1384 1011/1995 1W111995 EmpW OOB
r-iiE Ill302 CN 1231 16/1/1995 16/1/1995 ErnpU 0052
C&No #13&5 CN 1356 M/1/1995 2Wlt1995 EmpU DO65
pi%r W1309 CN 3615 22/1/1995 2211/I995 ErwW 0094
Sald)ak W1315CN1651 26/1/1995 26/1/1995 EmpU 012'1
112/1995 W1317CN1984 1/2/1995 Emp# 0138
ShpDae #1350 CN 3052 lm995 112/1995 Empll0071
Itundslned, W1355 CN 3 3 3 5/2/1 995 5/2/1995 EmpW 0141
W1860 CN 3615 4/2/1996 <un&lmeb
Emflo
IEmp#0138

Figura 13.10. En el ejernplo NullDates se controlan 10s eventos OnGetText y


OnSetText de un carnpo de fecha.

- -- --- - - - - - - -

ADYERTENCIA: La gestion de valores nulos en Delphi 6 y 7 puede verse


afectada por cambios en el mod0 de funcionamiento de las variantes nulas.
Comparar un campo con un valor nulo con otro carnpo tiene un efecto
distinto en las ultimas versiones de Delphi que en el pasado. En Delphi 7 se
pueden usar variables globales para ajustar el efecto de las comparaciones
I en las que intervengan variantes.

Navegacion por un conjunto de datos


Hemos visto que un conjunto de datos solo tiene un registro activo; este regis-
tro activo suele cambiar en respuesta a las acciones del usuario o debido a orde-
nes internas al conjunto de datos. Para desplazarnos por un conjunto de datos y
cambiar el registro activo, existen metodos de la clase T D a t a S e t , como se vio
en el listado 13.2 (en particular en la seccion comentada como posici611, mo-
v i m i e n t 0). Podemos movernos a1 registro siguiente o a1 anterior, volver atras o
adelante por una serie de registros datos (con MoveBy) o ir directamente a1
primer0 o a1 ultimo registro del conjunto de datos. Estas operaciones del conjunto
de datos estan, por lo general, disponibles gracias a1 componente DBNavigator y
a las acciones estandar de 10s conjuntos de datos y no resultan especialmente
complejas de comprender.
La forma en que un conjunto de datos controla las posiciones extremas no
resulta tan obvia. Si abrimos cualquier conjunto de datos con un navegador aso-
ciado, podemos ver que a medida que nos movemos registro por registro, el boton
Next permanece activado incluso cuando hemos alcanzado el ultimo registro.
~ n i c a m e n t ecuando intentamos movernos hacia delante, despues del ultimo regis-
tro, se desactiva el boton (y no cambia el registro actual). Esto se debe a que la
comprobacion E o f (de fin de archivo) solo resulta satisfactoria cuando el cursor
se ha movido a una posicion especial despues del ultimo registro. Si saltamos a1
final con el boton Last, en cambio, pasaremos inmediatamente a1 final del todo.
En el caso del primer registro (y de la comprobacion Bof), veremos exactamente
el mismo comportamiento. Ademas de movernos registro por registro o por un
numero dado de registros, 10s programas podrian necesitar saltar a registros o
posiciones concretas. Algunos conjuntos de datos soportan la propiedad
R e c o r d c o u n t y permiten el movimiento a un registro situado en una pocion
especifica del conjunto de datos utilizando la propiedad RecNo. Estas propieda-
des solo se pueden usar en el caso de conjuntos de datos que soporten posiciones
de forma nativa, lo cual excluye basicamente todas las arquitecturas clientelservi-
dor, a menos que llevemos todos 10s registros a la cache local (algo que normal-
mente querremos evitar) y, a continuacion, naveguemos por la cache. Cuando se
abre una consulta en un servidor SQL, solo se consiguen 10s registros que se usan,
de manera que Delphi no conoce la cuenta de registros (a1 menos no de antemano).
Existen dos alternativas que podemos usar para referirnos a un registro de un
conjunto de datos, sea cual sea su tipo:
Podemos guardar una referencia a1 registro actual y, a continuacion, vol-
ver de nuevo a el despues de habernos movido por otros registros. Para ello
hay que utilizar marcadores ya sea TBoo kmar k o TBoo kmar kS t r , que
es mas moderno, como explicaremos mas adelante.
Podemos encontrar un registro del conjunto de datos que se ajuste a una
serie de criterios, utilizando el metodo L o c a t e . Esto funciona incluso
tras haber cerrado y abierto de nuevo el conjunto de datos? porque trabaja-
mos a un nivel logico (no fisico).

El total de una columna de tabla


Hasta ahora, en 10s ejemplos, el usuario puede visualizar el contenido actual
de una tabla de base de datos y editar manualmente 10s datos o introducir nuevos
registros. Ahora veremos como modificar 10s datos de la tabla mediante el codigo
del programa. El conjunto de datos de empleado que ya hemos utilizado tiene un
campo Salary, para que el administrador de la empresa pueda revisar la tabla y
modificar el sueldo de un unico empleado. Consideremos ahora lo que sucede con
el coste total en sueldos para la empresa y el caso de que el administrador quiera
aumcntar (o disminuir) en un 10 por ciento el sueldo de todos 10s empleados.
El programa, que tambien muestra el uso de una lista de acciones para las
acciones estandar de conjuntos de datos, tiene botones para calcular la suma de
10s sueldos actuales y modificarlos. La accion Total permite calcular la suma
de 10s sueldos de todos 10s empleados. Basicamente, se necesita analizar la tabla,
leyendo el valor del campo c d s s a l a r y para cada registro.
var
Total: Real;
begin
Total : = 0 ;
cds. First;
w h i l e n o t cds.EOF do
begin
Total : = Total + cdsSalary.Value;
cds .Next;
end;
MessageDlg ( ' S u m o f new s a l a r i e s is ' +
Format ( ' %m', [Total]) , mtInformation, [rnbok] , 0 ) ;
end

Este codigo funciona, como muestra la figura 13.1 1. pero tiene algunos pro-
blemas. Uno de ellos es que el puntero de registro se desplaza a1 ultimo registro,
por lo que se pierde la posicion anterior en la tabla. Otro problema consiste en que
la interfaz de usuario se refresca muchas veces durante la operation.

Figura 13.11. La salida del programa Total, que muestra 10s salarios totales de 10s
empleados.

Uso de marcadores
Para evitar estos dos problemas, hay que desactivar las actualizaciones y guar-
dar tambien la posicion actual del puntero de registro en una tabla y recuperarla
a1 final. Para ello, podemos usar un marcador de tabla, una variable especial que
guarda la posicion de un registro en una tabla de conjunto de datos. El enfoque
tradicional de Delphi consiste en declarar una variable del tipo de datos
T B o o kmar k e iniciarla mientras se obtiene la posicion actual de la tabla:
var
Bookmark: TBookmark;
begin
Bookmark : = cds.GetBookmark;

A1 final del metodo Act io nTot a lExe cut e, podemos recuperar la posi-
cion y borrar el marcador con las dos sentencias siguientes (dentro de un bloque
final 1 y para asegurarnos de que se libera la memoria del puntero):
cds.GotoBookmark (Bookmark);
cds.FreeBookmark (Bookmark);

Como una alternativa mejor (y mas actualizada), podemos usar la propiedad


Bookmark de la clase TDataset, que se refiere a un marcador que se elimina
au tomaticamente.
Esta propiedad se implementa tecnicamente como una cadena opaca, una es-
tructura que depende de la gestion de la vida efectiva de una cadena, pero no es
una cadena, por lo que se supone que no miramos lo que hay en ella. Podemos
modificar el codigo anterior del siguiente modo:
var
Bookmark: TBookmarkStr;
begin
Bookmark : = cds.Bookmark;
...
cds.Bookmark : = Bookmark:

Para evitar el otro efecto secundario del programa (vemos 10s registros despla-
zandose mientras la rutina recorre 10s datos), podemos desactivar temporalmente
10s controles visuales conectados con la tabla. La tabla tiene un metodo
Disablecontrols a1 que podemos llamar antes de que se inicie el bucle
while y un metodo Enablecontrols a1 que podemos llamar a1 final, des-
pues de que se recupere el puntero de registro.

TRUCO:Desactivar 10s controles data-aware conectados con un conjutrto


de datos durante las operaciones largas no solo mejora la interfaz de nwa-
rio (puesto que la salida no cambia constantemente), she que ademis acelera
considerablemente el programa. De hecho, el tiempo empleado en acttlali-
zar la interfaz de usuario es mucho mayor que d empleado en realizar 10s
calculos. Para cornprobarlo, se pueden oomentar tas llamadm a 10s m&-
dos DisableContrals y Enablecontra1 81 ejempfd T d ~ 9 1 -
jarse en la diferencia de velocidad.
Por ultimo, nos enfrentamos a cuantos peligros de error a1 leer 10s datos de la
tabla, sobre todo si el programa esta leyendo 10s datos desde un servidor a traves
una red. Si surge un problema cualquiera mientras se consiguen 10s datos, se crea
una excepcion, 10s controles permanecen desactivados y el programa no puede
recuperar su comportamiento normal. Para evitar esta situacion, deberiamos uti-
lizar un bloque t r y / f i n a l l y . En realidad, si queremos que el programa sea
fiable y a prueba de errores a1 cien por cien, deberiamos utilizar dos bloques
t r y / f i n a l l y anidados. Veamos el codigo en el que se han introducido estos
cambios:
procedure TSearchForm.ActionTotalExecute(Sender: TObject);
var
Bookmark: TBookmarkStr;
Total: Real;
begin
Bookmark : = cds.Bookmark;
t r~
cds.DisableContro1s;
Total : = 0;
try
cds. First;
while not cds.EOF do
begin
Total : = Total + cdsSalary.Value;
cds .Next;
end;
finally
cds.EnableControls;
end
finally
cds.Bookmark : = Bookmark;
end ;
MessageDlg ( 'Sum o f n e w s a l a r i e s is ' +
Format ( ' %ml , [Total]) , mt Information, [mbOK] , 0 ) ;
end;

NOTA: Hemos escrito este codigo para mostrar un ejemplo de bucle con el
que recorrer el contenido de una tabla, per0 conviene tener en cuenta que
existe una tecnica alternativa basada en el uso de una consulta SQL, que
devuelve la suma de valores de un campo. Cuando usamos un servidor
SQL, la ventaja de velocidad de una llakada SQL para calcular el total
puede ser significativa, dado que no es necesario lleiar todos 10s datos de
- . - . - - . - - - -. -- .- .-
cada camDo desde el S e ~ d 0 a1 r ordenador del cliente. El serv~dorsolo
envia al cliente el resultado final. Existe una alternativa mejor cuando se
usa un ClientDataSet, ya que aislar una columna es una de las carac-
teristicas
- - - - - -- - -- m e ofrecen 10s aerenados. Lo
1--------- --- -=--=----. -- m
---e hemos comentado aaui
--------I--
---- es
-- una
solution generics, que deberia funcionar para cualquier conjunto de datos.
Edicion de una columna de tabla
El codigo de la accion de increment0 es similar a1 que ya hemos visto. El
metodo A c t i o n 1 n c r e a s e E x e c u t e tambien recorre la tabla, calculando el
total de 10s sueldos, como hacia el metodo anterior. Aunque solo tiene dos
parametros, esiste una diferencia clave. Cuando aumentamos el sueldo, estamos
cambiando 10s datos de la tabla. Las dos sentencias clave se encuentran dentro del
bucle w h i l e :
w h i l e n o t cds. EOF do
begin
c d s .Edit;
cdsSalary.Value : = Round (cdsSalary.Value * S p i n E d i t l - V a l u e )
/ 100;
Total : = Total + cdssalary-Value;
c d s .Next;
end:

La primera sentencia pone la tabla en cl mod0 de edicion, de mod0 que 10s


cambios de 10s campos tengan un efecto inmediato. La segunda sentencia calcula
el nuevo sueldo, multiplicando el antiguo por el valor del componente S p i n E d i t
(de manera predeterminada, 105) y dividiendolo entre cien. Eso significa un au-
mento del cinco por ciento, aunque 10s valores se redondean de acuerdo con el
valor mas prosimo en dolares. En este programa, podemos modificar 10s sueldos
en una cantidad cualquiera, incluso duplicar el salario de cada empleado haciendo
clic sobrc un boton.
- -

ADVERTENCIA: Observe que la tabla entra en el mod0 de ehcion cada


vez que se ejecuta el bucle w h i l e . Esto se debe a que en un conjunto de
datos las operaciones de edicion solo pueden aplicarse a un registro cada
vez. Tendremos que terminar la operacion de edicion, Ilamando a p o s t o
pasar a un regist& diferente cornokn el c6digo anterior. En ese momento, si
queremos carnbiar a otro registro, tenemos que volver a entrar en el modo
de edicion.

Personalization de la cuadricula de una base


de datos
A diferencia de la mayoria de 10s demas controles data-aware, que son bastan-
te sencillos de usar, el control DBGrid posee muchas opciones y es mas potente
de lo que pudiera parecer. Los siguientes apartados exploran algunas operaciones
avanzadas que podemos realizar utilizando un control DBGrid.El primer ejem-
plo mostrara como pintar en una cuadricula y el segundo como usar la caracteris-
tica de selection multiple de la cuadricula.

Pintar una DBGrid


Puede que queramos personalizar el aspect0 de una cuadricula (grid).Un buen
ejemplo es el de resaltar campos o registros especificos. Otro consiste en ofrecer
alguna forma de salida para 10s campos que normalmente no aparecen en la cua-
dricula, como 10s campos BLOB, grafico y de memo.
Para personalizar por completo la representacion de un control DBGrid, te-
nemos que definir su propiedad DefaultDrawing como False y controlar
su evento OnDrawColumnCe11. Si dejamos el valor Default Drawing como
True, la cuadricula mostrara la salida predefinida antes de llamar al metodo. De
este modo, todo lo que podemos hacer es aiiadir algo a la salida predefinida de la
cuadricula, a menos que decidamos dibujar sobre ella, lo cual nos llevaria mas
tiempo y produciria un parpadeo.
La tecnica alternativa consiste en llamar a1 metodo DefaultDrawCo-
lumncell de la cuadricula, tal vez cambiando la fuente actual o restringiendo
el rectangulo de salida. En este ultimo caso, podemos ofrecer una representacion
adicional en una celda y dejar que la cuadricula cubra la zona restante con la
salida estandar. Asi lo hemos hecho en el programa DrawData.
El control DBGrid de este ejemplo, que esta conectado a la tabla Biolife
clasica de Borland, tiene las siguientes propiedades:
o b j e c t DBGridl : TDBGrid
Align = alClient
Datasource = DataSourcel
DefaultDrawing = False
OnDrawColumnCell = DBGridlDrawColumnCell
end

Se llama al controlador de eventos OnDrawColumnCe 11 una vez para cada


celda de la cuadricula. Este controlador tiene diversos parametros, como el rec-
tangulo correspondiente a la celda, el indice de la columna que tenemos que dibu-
jar, la propia columna (con el campo, su alineacion y otras subpropiedades) y el
estado de la celda.
Para establecer el color de celdas especificas como rojo, podemos cambiarlo
en 10s casos especiales:
procedure TForml.DBGridlDrawColumnCe11(Sender: TObject;
c o n s t Rect : TRect; DataCol : Integer; Column: TColumn;
State: TGridDrawState) ;
begin
// color d e fuente rojo, s i la longitud > 1 0 0
if (Column.Field = cdslengthcm) and (cdsLengthcm-AsInteger> 100)
then
DBGridl.Canvas.Font.Color : = clRed;
// d i s e d o p r e d e f i n i d o
DBGridl.DefaultDrawDataCel1 (Rect, Column.Field, State);
end;

El siguiente paso consiste en dibujar 10s campos de memo y graficos. Para el


memo, podemos simplemente implementar 10s eventos OnGetText y OnSetText
del campo memo.
La cuadricula permite incluso editar en un campo de memo si el evento
OnSetText es distinto de nil.Este es el codigo de 10s dos controladores de
eventos. Hemos usado Trim para eliminar 10s caracteres finales que no se impri-
men, que hacen que el texto parezca estar en blanco a1 editar:
procedure TForml.cdsNotesGetText(Sender: TField;
var Text: String; DisplayText: Boolean) ;
begin
Text : = Trim (Sender.Asstring);
end;

procedure TForml.cdsNotesSetText(Sender: TField; const Text:


String) ;
begin
Sender-AsString : = Text;
end;

Para la imagen, la tecnica mas sencilla consiste en crear un objeto TPicture


temporal, asignarle el campo grafico y pintar el mapa de bits sobre el lienzo de la
cuadricula. Como alternativa, hemos eliminado el campo grafico de la cuadricu-
la, definiendo su propiedad Visible como Fa1se y aiiadido la imagen a1 nom-
bre del pez, con el siguiente codigo adicional en el controlador de evento
OnDrawColumnCell:
var
Picture: TPicture;
OutRect : TRect;
PictWidth: Integer;
begin
// r e c t d n g u l o d e s a l i d a p r e d e f i n i d o
OutRect : = Rect:

if Column-Field = cdsComrnon-Name then


begin
// d i b u j a l a imagen
Picture : = TPicture.Create;
try
Picture.Assign (cdsGraphic);
PictWidth : = (Rect-Bottom - Rect.Top) * 2;
0utRect.Right : = Rect.Left + PictWidth;
DBGrid1.Canvas.StretchDraw (OutRect, Picture.Graphic);
finally
Picture. Free;
end;
/ / redefinir el rectdngulo d e salida, dejando espacio para
/ / el grdfico
OutRect : = Rect;
0utRect.Left : = 0utRect.Left + PictWidth;
end;

/ / color d e fuente rojo, si la longitud > 100 (omitido,


/ / vease anterior)

/ / dibujo predefinido
DBGridl.DefaultDrawDataCel1 (OutRect, Column.Field, S t a t e ) ;

Como podemos ver en el codigo anterior, el programa muestra la imagen en un


pequeiio rectangulo a la izquierda de la celda de la cuadricula y, a continuacion,
cambia el rectangulo de salida a la zona restante antes de activar el dibujo
predefinido. Podemos ver el efecto en la figura 13.12

1s p e w s No I c&cP ICummnOmmMNama 15ceclcr ~ a w


1 90020 Triggerfish Clown Triggerfish Ball~sloidesconspicillum
90030 6 ~ e ~dm p e r o r Lutjanus sebae
90050 Wresse Z ~Glant J Maor1 Wrasse Che~l~nus undulatus
90070 Angelf~sh Blue Angell~sh Pomacanlhus nauarchus
90080 Cod SW Lunarta~lRockcod Var~olalout1
90090 Scorp~onfish F~ref~sh Plerois vol~tans
90100 Bunefflylish 6-3 Ornate Bunefflylish ,Chaetodon Ornatissimus
901 10 Shark -Swell Shark Cephaloscyllium ventriosum
90120 Ray += 6.1 Ray Myhobatis californica
90130 Eel " -7California Moray Gymnothorax mordax
90140 Cod 4-- Lingmd Ophiodon elongatus

Figura 13.12. El programa DrawData muestra una cuadricula que incluye el texto de
un campo de memo y el omnipresente pez de Borland.

Una cuadricula que permite la seleccion multiple


El segundo ejemplo de personalization del control D B G r i d tiene que ver con
la seleccion multiple. Podemos preparar el control DBGrid de tal mod0 que el
usuario pueda seleccionar diversas filas (es decir, diversos registros). Esto resul-
ta muy sencillo, puesto que todo lo que hay que hacer es activar el elemento
d g M u l t i s e l e c t de la propiedad opt i o n s de la cuadricula. Cuando haya-
mos seleccionado esta opcion, el usuario puede mantener pulsada la t e c h Control
y hacer clic con el raton para seleccionar varias filas de la cuadricula, obteniendo
el efecto que se muestra en la figura 13.13.
Dado que la tabla del conjunto de datos solo puede tener un registro activo, la
cuadricula guarda simplemente una lista de marcadores a 10s registros seleccio-
nados. Esta lista esta disponible en la propiedad S e l e c t e d R o w s , que es del
tipo T B o o kmar k L i s t . Ademas de acceder a1 numero de objetos de la lista con
la propiedad C o u n t , podemos acceder a cada marcador con la propiedad It e m s ,
que es la propiedad de matriz predefinida. Cada elemento de la lista es de un tip0
TBoo k m a r k S t r , que representa un punter0 de marcador que podemos asignar
a la propiedad Bookmark de la tabla.

La Paz

Guyana IGso~gelor
b? Sodh Arne
Jarna~ca Kmgston North Amer
Memco Cty NorlhAmer
Managua NorlhAmer
Asuncion South A m c d

Figura 13.13. El ejemplo MltGrid tiene un control DBGrid que permite la seleccibn de
varias filas.

NOTA: TBoo k m a r kStr es un tipa cadena de conveniencia, pero sus


datos deberian considerarse "opacos" y volatiles. No deberiamos confiar en
ninguna estructura concreta de datos que podamos encontrar, si nos fija-
mos un valor de marcador, ni deberiamos guardar 10s datos durante dema-
siado tiempo o almacenarlos en un archivo independiente. Los datos de
marcador variaran segun el controlador de la base de datos y la configura-
cion del indice y pueden resultar inutiles cuando nosotros o 10s usuarios de
la base de datos afiadamos o eliminemos filas del conjunto de datos.

Veamos el codigo del ejemplo MltGrid; que se activa a1 hacer clic sobre el
boton para mover el campo N a m e de 10s registros seleccionados a1 cuadro de
lista:
procedure TForml.ButtonlClick(Sender: TObject);
var
I: Integer;
BookmarkList: TBookmarkList;
B o o k m a r k : TBookmarkStr;
begin
// almacena l a p o s i c i o n a c t u a l
B o o k m a r k : = cds.Bookmark;
try
// v a c i a e l c u a d r o d e l i s t a
ListBoxl.Items.Clear;
// o b t i e n e l a s f i l a s s e l e c c i o n a d a s d e l a c u a d r i c u l a
BookmarkList : = DbGridl-SelectedRows;
for I : = 0 to BookmarkList.Count - 1 do
begin
// p a r a c a d a u n o , m u e v e l a t a b l a a e s e r e g i s t r o
cds.Bookmark : = BookmarkList[I];
// a i i a d e e l carnpo name d l c u a d r o d e l i s t a
ListBoxl. Items .Add (cds.FieldByName ( ' N a m e ' ) .Asstring);
end;
finally
// v u e l v e a 1 r e g i s t r o i n i c i a l
cds.Bookmark : = Bookmark;
end;
end;

Arrastre sobre una cuadricula


Otra tecnica interesante es el uso del arrastre con cuadriculas. Arrastrar desde
una cuadricula no resulta especialmente dificil, puesto que conocemos el registro
actual y la columna que ha seleccionado el usuario. En cambio, el arrastre a una
cuadricula resulta aparentemente dificil para el programa. A continuacion, usare-
mos una tecnica denominada "hackprotegido" para implementar el arrastre sobre
la cuadricula.
En el ejemplo, llamado DragToGrid, tenemos una cuadricula conectada a1
conjunto de datos de paises, una casilla de edicibn en la que podemos escribir el
nuevo valor para un carnpo y una etiqueta que podemos arrastrar sobre una celda
de la cuadricula para modificar el campo relacionado. El verdadero problema
consiste en como determinar este campo. El codigo tiene unas pocas lineas, como
se puede ver, per0 resulta algo criptico y merece una explicacion:
type
TDBGHack = class (TDbGrid)
end;

procedure TFormDrag.DBGridlDragDrop(Sender, Source: TObject; X,


Y: Integer);
var
gc : TGridCoord;
begin
gc := TDBGHack (DbGridl).MouseCoord (X, Y) ;
if (gc.y > 0) and (gc.x > 0) then
begin
DBGrid1.DataSource.DataSet.MoveBy (gc-y -
TDBGHack (DBGridl).Row) ;
DBGrid1.DataSource.DataSet.Edit;
DBGridl.Columns.Items [gc.x - 1l.Field.AsString :=
EditDrag.Text;
end;
DBGridl-SetFocus;
end;
La primera operacion es decidir sobre que celda se solto el boton del raton.
Partiendo de las coordenadas X e Y del raton, podemos llamar a1 metodo protegi-
do M o u s e C o o r d para acceder a la fila y a la columna de la celda. A menos que
el destino del arrastre sea la primera fila (normalmente la que contiene 10s titulos)
o la primera columna (normalmente la contiene el indicador), el programa mueve
el registro actual por la diferencia entre la fila solicitada (gc. y) y la fila activa
en ese momento (la propiedad protegida Row de la cuadricula). El siguiente paso
consiste en poner el conjunto de datos en mod0 de edicion, capturar el campo de la
columna de destino ( C o l u m n s . I terns [ g c .x - 1] . F i e l d ) y modificar su
texto.

Aplicaciones de bases de datos con


controles estandar
Aunque normalmente es mas rapido escribir aplicaciones Delphi basadas en
controles data-aware, en realidad esto no resulta necesario. Cuando necesitamos
tener un control concreto sobre la interfaz de usuario de una aplicacion de base de
datos, podriamos querer personalizar la transferencia de 10s datos desde 10s obje-
tos de campo a 10s controles visuales. Una forma de verlo es que esto solo resulta
necesario en algunos casos especificos, porque se pueden personalizar en gran
medida 10s componentes data-aware configurando sus propiedades y controlando
10s eventos de 10s objetos de campo. Sin embargo, tratar de trabajar sin 10s con-
troles data-aware deberia ayudar a comprender mejor el comportamiento
predefinido de Delphi. El desarrollo de una aplicacion no basada en controles
data-aware puede seguir dos enfoques distintos. Podemos imitar el cornporta-
miento estandar de Delphi mediante codigo, separandonos posiblemente del mis-
mo en casos especificos o podemos usar un enfoque mucho mas personalizado. El
ejemplo NonAware ilustra la primera tecnica y el ejemplo SendToDb la ultima.

Imitacion de 10s controles data-aware de Delphi


Para crear una aplicacion que no use controles data-aware pero que se com-
porte como una aplicacion estandar de Delphi, podemos, sencillamente, escribir
controladores de eventos para las operaciones que 10s controles data-aware reali-
zarian automaticamente. Basicamente, hay que poner a1 conjunto de datos en
mod0 de edicion cuando el usuario modifique el contenido de 10s controles visua-
les y actualizar 10s objetos de campo del conjunto de datos cuando el usuario
abandone 10s controles, llevando el foco a otro elemento.
El otro elemento del ejemplo NonAware es una lista de botones que corres-
ponden a algunos de 10s que se encuentran en el control DBNavigator; estos
botones estan conectados a cinco acciones personalizadas. No podemos usar las
acciones de conjuntos de datos estandar en este ejemplo, sencillamente porque se
conectan automaticamente con la fuente de datos asociada con el control que tiene
el foco, un mecanismo que falla en 10s cuadros de edicion no data-aware de este
ejemplo. En general tambien se podria conectar una fuente de datos con la propie-
dad Datasource de cada una de las acciones, pero en este caso especifico no
tenemos una fuente de datos.
El programa tiene varios controladores de eventos que no se han usado en las
aplicaciones anteriores que utilizaban controles data-aware. En primer lugar, hay
que mostrar 10s datos del registro actual en 10s controles visuales (vease figura
13.14) controlando el evento O n A f terScro11 del componente de conjunto de
datos.
procedure TForml.cdsAfterScroll (Datasender: TDataSet);
begin
EditName.Text : = cdsName.AsString;
EditCapital.Text : = cdsCapital.AsString;
ComboContinent.Text : = cdsContinent.AsString;
EditArea.Text : = cdsArea.AsString;
EditPopu1ation.Text : = cdsPopu1ation.AsString;
end;

Figura 13.14. La salida del ejernplo NonAware en el m o d ~


de navegacion. El
programa consigue rnanualrnente 10s datos cada vez que cambia el registro actual.

El controlador del evento Onstatechange del control muestra el estado de


la tabla en un control de barra de estado. Cuando el usuario escribe en uno de 10s
cuadros de edicion o despliega la lista del cuadro combinado, el programa pone la
tabla en mod0 de edicion:
p r o c e d u r e TForml .EditKeyPress (Sender: TObject; v a r Key: Char) ;
begin
i f n o t (cds .State i n [dsEdit, dsInsert] ) t h e n
c d s .Edit;
end;

Este metodo esta conectado a1 evento OnKe y p r e s s de 10s cinco componentes


y es similar a1 controlador de 10s eventos OnDropDown del cuadro combinado.
Cuando el usuario abandona uno de 10s controles visuales, el controlador del
evento O n E x i t copia 10s datos a1 campo correspondiente, como en este caso:
procedure TForml.EditCapitalExit(Sender: TObject);
begin
i f (cds .State i n [dsEdit, dsInsert] ) then
cdsCapital.AsString : = E d i t c a p i t a l - T e x t ;
end:

La operacion solo se realiza si la tabla esta en mod0 de edicion, es decir, solo


si el usuario ha escrito en este u otro control. No se trata del comportamiento
ideal, porque se realizan operaciones adicionales incluso si no se ha modificado el
texto del cuadro de edicion; sin embargo, 10s pasos adicionales son lo suficiente-
mente rapidos como para que suponga un problema. En el caso del primer cuadro
de edicion, verificamos el texto antes de copiarlo, creando una excepcion si el
cuadro de edicion esta en blanco:
procedure TForml.EditNameExit(Sender: T O b j e c t ) ;
begin
i f (cds.State i n [dsEdit, dsInsert] ) then
i f EditName.Text <> " then
cdsName.AsString : = EditName.Text
else
begin
EditName-SetFocus;
r a i s e Exception. C r e a t e ( ' U n d e f i n e d C o u n t r y ' ) ;
end;
end;

Una tecnica alternativa para comprobar el valor de un campo consiste en con-


trolar el evento B e fo r e P o s t del conjunto de datos. Conviene tener en cuenta,
en este ejemplo, que la operacion de notificacion no se controla mediante un boton
especifico, sino que tiene lugar en cuanto el usuario se mueve a un nuevo registro
o inserta uno nuevo:
procedure TForml.cdsBeforePost(DataSet: T D a t a S e t ) ;
begin
i f cdsArea.Value < 100 then
r a i s e Exception.Create ( ' A r e a t o o small' ) ;
end ;

En cada uno de estos casos, existe una alternativa a la creacion de una excep-
cion que consiste en establecer un valor predefinido. Sin embargo, si un campo
tiene un valor predefinido, es mejor presentarlo, para que el usuario pueda ver
que valor se enviara a la base de datos. Para ello, podemos controlar el evento
After Insert del conjunto de datos, que ocurre inrnediatamente despues de
que se haya creado un nuevo registro (podiamos haber usado tambien el evento
OnNewRecord ):
procedure TForml.cdsAfterInsert(DataSet: TDataSet);
begin
cdsContinent.Va1ue : = 'Asia';
end;

Envio de solicitudes a la base de datos


Podemos personalizar mas la interfaz de usuario de una aplicacion si decidi-
mos controlar la misma secuencia de operaciones de edicion que en 10s controles
data-aware estandar de Delphi. Este enfoque ofrece una libertad total, aunque
podria provocar algunos efectos secundarios (como una capacidad limitada de
controlar la concurrencia). Para este nuevo ejemplo, hemos reemplazado el pri-
mer cuadro de edicion por otro cuadro combinado y sustituido todos 10s botones
relacionados con las operaciones de la tabla (que correspondian a botones de
DBNavigator) por dos botones personalizados, utilizados para obtener datos de
la base de datos y enviarle una actualizacion. Una vez mas, este ejemplo no tiene
un componente Dat aSource.El metodo Get Da t a, conectado a1 boton corres-
pondiente, obtiene sencillamente 10s campos correspondientes a1 registro indicado
en el primer cuadro combinado:
procedure TForml-GetData;
begin
cds .Locate ('Name', ComboName .Text, [loCaseInsensitive]) ;
ComboName.Text : = cdsName.AsString;
EditCapital.Text, : = cdsCapita1.AsString;
ComboContinent.Text : = cdsContinent.AsString;
EditArea.Text : = cdsArea.AsString;
EditPopulation.Text : = cdsPopu1ation.AsString;
end:

Se llama a este metodo siempre que el usuario hace clic sobre el boton, selec-
ciona un elemento del cuadro combinado o pulsa la tecla Intro mientras que se
encuentra en el cuadro de dialogo:
procedure TForml.ComboNameClick(Sender: TObject);
begin
GetData;
end;

procedure TForml.ComboNameKeyPress(Sender: TObject; var Key:


Char) ;
begin
if Key = $13 then
GetData;
end;
Para que el ejemplo funcione rapidamente, a1 arrancar el cuadro combinado se
rellena con 10s nombres de todos 10s paises de la tabla:
p r o c e d u r e TForml.FormCreate(Sender: TObject);
begin
// r e l l e n a l a l i s t a de nombres
c d s .Open;
w h i l e n o t cds.Eof d o
begin
ComboName.Items.Add (cdsName.AsString);
cds .Next;
end ;
end ;

Con esta tecnica, el cuadro combinado se transforma en una especie de selector


para el registro, como muestra la figura 13.15. Gracias a esta seleccion, el pro-
grama no necesita botones de navegacion.

Jamaica

&= 1756943

Figura 13.15. En el ejernplo SendToDb se puede usar un cuadro combinado para


seleccionar el registro que se desea ver.

Por ultimo, el usuario puede modificar 10s valores de 10s controles y hacer clic
sobre el boton Send. El codigo que se va a ejecutar depende de si la operacion es
una actualizacion o una insercion. Podemos saberlo fijandonos en el nombre (aun-
que con este codigo, un nombre erroneo ya no podra modificarse):
p r o c e d u r e TForml.SendData;
begin
// crea una e x c e p c i d n , s i no hay nombre
i f ComboName.Text = " then
r a i s e Exception .Create ( ' I n s e r t t h e name' ) ;

/ / v e r i f i e d s i e l r e g i s t r o ya e s t d e n l a t a b l a
i f cds .Locate ( ' N a m e ' , ComboName .Text, [loCaseInsensitive] )
then
begin
/ / modified e l r e g i s t r o encontrado
cds.Edit;
cdsCapita1.AsString : = E d i t c a p i t a l - T e x t ;
cdsContinent.AsString : = ComboContinent.Text;
cdsArea.AsString : = E d i t A r e a - T e x t ;
cdsPopu1ation.AsString : = EditPopulation.Text;
cds .Post;
end
else
begin
/ / inserta un nuevo registro
cds.InsertRecord ([ComboName.Text, EditCapital.Text,
ComboContinent.Text, EditArea.Text,
EditPopulation.Text1);
// a d a d e a la lista
.
ComboName Items .Add (ComboName.Text)
end;

Antes de enviar 10s datos a la tabla podemos realizar cualquier tipo de prueba
de validacion a 10s valores. En este caso, no tiene mucho sentido controlar 10s
eventos de 10s componentes de la base de datos, porque tenemos control total
sobre el momento en el que se realizan las operaciones de actualizacion o inser-
cion.

Agrupacion y agregados
Ya hemos visto que un ClientDataSet puede tener un indice distinto a partir del
orden en que se guardan 10s datos en el archivo. Una vez que se define un indice,
se pueden agrupar 10s datos de acuerdo con ese indice. En la practica, un grupo
esta definido como una lista de registros consecutivos (segun su indice) para 10s
cuales no cambia el valor del campo indexado. Por ejemplo, si tenemos un indice
para la provincia, todas las direcciones de esa provincia entraran en el mismo
grupo.

Agrupacion
El ejemplo CdsCalcs tiene un componente C l i e n t D a t a S e t que extrae sus
datos del habitual conjunto de datos c o u n t r y . c d s .
El grupo se consigue, junto con la definicion de un indice, a1 especificar un
nivel de agrupacion para el indice:
object ClientDatasetl: TClientDataSet
IndexDefs = <
item
Name = ' Clientda taSetl Index1
Fields = 'Continent'
GroupingLeve = 1
en&
IndexName = 'ClientDataSetlIndexl'
Cuando se activa un grupo, se puede hacer obvio para el usuario si se muestra
la estructura de agrupacion en el control DBGrid, como muestra la figura 13.16.
Todo lo que hay que hacer es controlar el evento O n G e t T e x t para el campo
agrupado (en el ejemplo, el campo C o n t i n e n t ) y mostrar el texto solo si el
registro es el primer0 del grupo:
procedure TForml.ClientDataSetlContinentGetText(Sender: TField;
var Text: String; DisplayText: Boolean) ;
begin
i f gbFirst i n ClientDataSetl .GetGroupState (1) then
Text : = Sender.AsString.
else
Text : = ";
end;

Nicara~a
El S alvadar
Cuba
Jaaica
Un~tedSlates d Amarlca Washmglon
Canada Ollawa
S d h America Paraguay Amion
U~WW Monlevideo
Venezuela Caracas
Peru Lima
hgmfina B uermr Aiies
Guyana Georgetown
Ecuador Quito
Cob& Bwta
Chi 'Sancbju -

8rd Brasika

I ~. -. - - - - - - -- - - - - .--

Figura 13.16. El ejemplo CdsCalcs rnuestra que escribiendo un poco de codigo, se


puede conseguir que un control DBGrid rnuestre visualrnente la agrupacion definida
en el ClientDataSet.

Definicion de agregados
Otra caracteristica del componente C1i e n t D a t a S e t es el soporte de agre-
-
gados. Un agregado es un valor calculado basandose en varios registros, como la
&ma o el valorpromedio de un campo para toda la tabla o un gripe de registros
(definido mediante la logica de agrupacion ya comentada). Los agregados son
mantenidos, es decir, se vuelven a calcular inmediatamente si cambia uno de 10s
registros. Por ejemplo, se puede mantener automaticamente el total de una factura
mientras el usuario escribe 10s elementos de la factura.
lpd+ Es valores cada vez que &imbia an valor. La agregacih se aprove-
i '
&a de 10s datos delta de1 Cll&ntDataSet.~btjempb, para actualizar
un8 suma c u a n d ~cambi$-uij W p q el Clier)tQa,taSst resta e1 antiguo
v a h del agregadb y aiiade e1nqew valor. S&lo se neceskan dos cAlculos,
-90 aunque d a w m i l c s d i fileen esd a g r e d p . . i o r cste motivo,
las actualizaciones de agrega& son instazit6vleas.

Existen dos formas de definir agregados. Se puede usar la propiedad


A g g r e g a t e s del C l i e n t D a t a S e t , que es un conjunto, o se pueden definir
campos agregados mediante el editor Fields. En ambos casos, se define la expre-
sion agregada, se le da un nombre y se conecta con un indice y un nivel de
agrupamiento (a no ser que se desee aplicar a toda la tabla). Este es el conjunto
A g g r e g a t e s del ejemplo CdsCalcs:

o b j e c t ClientDataSetl: TClientDataSet
Aggregates = <
item
Active = True
AggregateName = ' C o u n t '
Expression = 'COUNT (NAME)'
GroupingLevel = 1
IndexName = ' C l i e n t D a t a S e t l I n d e x 1 0
Visible = False
end
item
Active = True
AggregateName = ' T o t a l P o p u l a t i o n '
Expression = 'SUM (POPULATION) '
Visible = False
end>
AggregatesActive = T r u e

Fijese en que en la ultima linea del anterior fragment0 de codigo se debe acti-
var el soporte para 10s agregados, ademas de activar especificamente cada agre-
gad0 que se quiera usar. Es importante inhabilitar 10s agregados, porque tener
muchos de ellos puede ralentizar un programa.
El enfoque alternativo es usar el editor Fields, escoger la orden N e w F i e l d
desde su menu de metodo abreviado y seleccionar la opcion Aggregate (disponi-
ble, junto con la opcion InternalCalc, solo en un ClientDataSet). Esta es la
definition de un campo agregado:
object ClientDataSetl: TClientDataSet
object ClientDataSetlTotalArea: TAggregateField
FieldName = ' T o t a l A r e a '
ReadOnly = True
Visible = True
Active = True
DisplayFormat = ' # # # , # # # , # # # I
Expression = ' S U M (AREA) '
GroupingLevel = 1
IndexName = ' ClientDa taSetl Index1 '
end

Los campos agregados se muestran en el editor Fields en un grupo indepen-


diente, como muestra la figura 13.17. La ventaja de usar un campo agregado, en
comparacion con un simple agregado, es que se puede definir el formato de repre-
sentacion y conectar directamente el campo con un control data-aware, como un
DBEd it en el ejemplo CdsCalcs. Ya que el agregado se encuentra conecta a un
grupo, en cuanto se selecciona un registro de un grupo distinto, se actualiza
automaticamente la salida. Ademas, se cambian 10s datos, el total mostrara inme-
diatamente el nuevo valor.

Figura 13.17. La parte inferior del editor Fields de un ClientDataSet muestra 10s
campos agregados.

Para usar agregados simples, hay que escribir algo de codigo, como en el
ejemplo siguiente (el V a l u e del agregado es una variante):
procedure TForml.ButtonlClick(Sender: TObject);
begin
Labell.Caption : =
'Area: ' + ClientDataSetlTotalArea.Disp1ayText +
#13'Population : ' +
FormatFloat ( ' # # # , # # # , # # # I , ClientDataSet1.Aggregates
[l] . v a l u e ) +
# 1 3 ' N u m b e r : ' + IntToStr (ClientDataSetl.Aggregates
[0] . V a l u e ) ;
end ;

Estructuras maestroldetalles
Es habitual que se necesite relacionar tablas que tengan una relacion uno a
muchos. Esto significa que, para un unico registro de la tabla maestra existen
muchos registros detallados en una tabla secundaria. Un ejemplo clasico de esto
es una factura y 10s elementos de la factura; otro es una lista de clientes y 10s
pedidos de cada uno de ellos.
Se trata de situaciones habituales en la programacion de bases de datos, y
Delphi ofrece un soporte explicit0 mediante la estructura maestro/detalles. La
clase TDat a S e t tiene un propiedad Data S our c e para configurar una fuente
de datos maestra. Esta propiedad se usa en un conjunto detallado para conectarse
a1 registro actual del conjunto de datos maestro en combinacion con la propiedad
MasterFields.

Maestroldetalle con 10s ClientDataSet


El ejemplo MastDet usa 10s conjuntos de datos de clientes y pedidos de mues-
tra. Hemos aiiadido un componente de fuente de datos para cada conjunto de
datos, y para el conjunto de datos secundario hemos asignado la propiedad
Datasource a la fuente de datos conectada a1 primer conjunto de datos. Por
ultimo, hemos relacionado la tabla secundaria con un campo de la tabla principal,
mediante el editor especial de la propiedad Master Fields.Hemos hecho todo
esto utilizando un modulo de datos.
Lo siguiente es el listado completo (pero sin las poco importantes propiedades
de posicionamiento) del modulo de datos usado por el programa MastDet:
o b j e c t DataModulel: TDataModulel
OnCreate = DataModuleCreate
o b j e c t dsCust : TDataSource
DataSet = cdsCustomers
end
o b j e c t dsOrd: TDataSource
DataSet = cdsorders
end
o b j e c t cdsorders: TClientDataSet
FileName = 'orders.cdsl
IndexFieldNames = ' Cus tNo'
MasterFields = 'CustNo'
Mastersource = dsCust
end
o b j e c t cdsCustomers: TClientDataSet
FileName = 'customers. cds '
end
end

En la figura 13.18 se puede ver un ejemplo del formulario principal del progra-
ma MastDet en tiempo de ejecucion. Hemos colocado 10s controles data-aware
relacionados con la tabla maestra en la parte superior del formulario, y una cua-
dricula conectada con la tabla de detalle en la parte inferior. De esta manera, para
cada registro maestro, se puede ver inmediatamente la lista de 10s registros de
detalle conectados (en este caso, todos 10s pedidos vinculados con el cliente ac-
tual). Cada vez que se selecciona un nuevo cliente, la cuadricula que se encuentra
bajo el registro maestro muestra solo 10s pedidos que pertenezcan a ese cliente.

4/12/1989

1280 1356 2511 211394 2611 211 994


1059 1356 2412/1983 25/2/1583
1W 1356 5/5/1339 6/5/1989 45
1305 1356201111995 2011/1995 65
;iLI
Figura 13.18. El ejernplo MastDet en tiernpo de ejecucion.

Control de errores de la base de datos


Otro elemento importante de la programacion de bases de datos consiste en
controlar 10s errores de la base de datos de forma personalizada. Por supuesto.
podemos dejar que Delphi muestre un mensaje de escepcion cada vez que h a p un
error en la base de datos, pero podriamos querer intentar corregir 10s errores o
simplemente que se nos mostrasen mas detalles. Basicamente hay tres tecnicas
que podemos usar para controlar 10s errores relacionados con bases de datos:
Podemos englobar en un bloque t r y / e x c e p t las operaciones de bases
de datos que resulten arriesgadas. Esto no es posible cuando la operacion
se crea mediante la interaccion con un control datn-aware.
Podemos instalar un controlador para el e v e n t o O n E x c e p t i o n del ob.jeto
global A p p l i c a t i o n .
Podemos controlar eventos especificos del con.junto de datos relacionados
con errores, como O n P o s t E r r o r , O n E d i t E r r o r , O n D e l e t e E r r o r
y OnUpdateError.
Mientras que la mayoria de las clases de escepcion en Delphi ofrecen un sim-
ple mensaje de error, las excepciones de base de datos suelen incluir una lista de
codigos de error, codigos de error originarios del servidor SQL, y datos similares.
El C l i e n t D a t a s e t aiiade un unico codigo de error a su clase de excepcion,
E D B C l i e n t . A1 mostrar como se controla esta excepcion, se ofrece una guia
para el resto de 10s casos.
Como ejemplo, hemos creado un programa de bases de datos que muestra 10s
detalles de 10s errores en un componente de memo (10s errores se generan
automaticamente cuando el usuario hace clic sobre 10s botones del programa).
Para controlar todos 10s errores, el ejemplo DBError instala un controlador para
el evento OnExcept ion del objeto global Application.El controlador del
evento registra algo de informacion en un campo de memo que muestra 10s deta-
lles del error de la base de datos si se trata de un EDBClient.
procedure TForml.ApplicationError (Sender: TObject; E: Exception);
begin
if E is EDBClient then
begin
Memol.Lines.Add('Error: ' + (E.Message));
Memol.Lines.Add(' Error Code: ' +
IntToStr (EDBClient (E).Errorcode)) ;
end
else
Memol.Lines.Add('Generic Error: ' + (E.Message));
end:
Clientel
sewidor
con dbExpress

En el capitulo anterior, hemos analizado el soporte de Delphi para la progra-


macion de bases de datos, empleando archivos locales (en particular, usando el
componente C 1i e n tDat aSet , o MyBase) en la mayoria de 10s ejemplos, per0
sin centrarnos en ninguna tecnologia de bases de datos concreta. Este capitulo
pasa a comentar el uso de las bases de datos servidores SQL, centrandonos en el
desarrollo clientelservidor con el BDE y la nueva tecnologia dbExpress. Un unico
capitulo no puede agotar completamente un tema como este, asi que lo presentare-
mos desde la perspectiva del desarrollador en Delphi y aiiadiremos algunos trucos
y sugerencias. Para 10s ejemplos, hemos usado InterBase, ya que esta RDMBS
(sistema de administracion de bases de datos relacionales) de Borland, o este
servidor SQL, se incluye con las versiones Professional y superiores de Delphi;
ademas, se trata de un servidor gratuito y de codigo abierto (aunque no en todas
las versiones). Analizaremos InterBase desde el punto de vista de Delphi, sin
profundizar en su arquitectura interna. Gran parte de la infonnacion aqui presen-
tada se aplica tambien a otros servidores SQL, por lo que, aunque decida no
utilizar InterBase, puede que siga siendo de interes.
En este capitulo trataremos 10s siguientes temas:
Vision global de la arquitectura clientelservidor.
Elementos del diseiio de bases de datos.
Presentacion de InterBase.
Programacion de servidor: vistas, procedimientos almacenados y
disparadores.
La biblioteca dbExpress.
Cache con el componente ClientDataSet.
Los componentes InterBase Express (IBX).

La arquitectura clientelservidor
Las aplicaciones de bases de datos de 10s capitulos anteriores utilizaban com-
ponentes nativos para acceder a 10s datos almacenados en archivos de un ordena-
dor local y cargar todo el archivo en memoria. Se trata de un enfoque extremo. De
manera mas tradicional, el archivo se lee registro a registro de manera que varias
aplicaciones puedan acceder a el a1 mismo tiempo, usando algunos mecanismos
de sincronizacion en la escritura.
Cuando 10s datos se encuentran en un servidor remoto, copiar toda una tabla
en memoria para procesarla es una tarea que consume tiempo y ancho de banda, y
suele resultar inutil. Como ejemplo, supongamos que tenemos una tabla como
EMPLOYEE (parte de la base de datos de ejemplo de InterBase, incluida con
Delphi) y que le aiiadimos miles de registros y la colocamos en un ordenador en
red como un servidor de archivos.
Si queremos conocer el maximo salario que paga la empresa, podemos abrir un
componente de tabla de dbExpress ( E m p T a b l e ) o una consulta de seleccion de
todos 10s registros y ejecutar este codigo:
EmpTable.Open;
EmpTable-First;
MaxSalary := 0;
while not EmpTable.Eof d o
begin
i f EmpTable. FieldByName ( ' S a l a r y ' ) .Ascurrency > MaxSalary
then
MaxSalary : = EmpTable. FieldByName ( ' S a l a r y ' ) .AsCurrency;
EmpTable .Next;
end;

El efecto de este enfoque es que lleva todos 10s datos de la tabla desde el
ordenador en red a1 ordenador local, una operacion que puede tomar minutos. En
este caso, el enfoque correct0 seria permitir que el servidor SQL calcule directa-
mente el resultado, devolviendo unicamente esta informacion. Puede hacerse esto
si se usa una sentencia SQL como:
select Max (Salary) from Employee
NOTA: Los dos fragmentos de cirdigo anteriores forman parte deI ejernplo
GetMax, que incluye cbdigo para cronometrar las dos tdcnicas. Para utiIizar
el componente Table de la pequefia tabla Employee se necesitan unas
diez veces m h de tiempo que para realizar la consulta, aunque el servidor
InterBase est6 instalado en el ordenador en que se ejecuta el programa.

Para almacenar una gran cantidad de datos en un ordenador central y no tener


que llevar 10s datos a 10s ordenadores cliente para procesarlos, la unica solucion
es permitir que el ordenador central manipule 10s datos y envie de vuelta a1 cliente
solo una cantidad limitada de informacion. Esta es la base de la programacion
clientelservidor.
Por lo general, utilizaremos un programa esistente en el servidor (un RDBMS)
y escribiremos una aplicacion de cliente personalizada que se conecte con el. Sin
embargo, algunas veces, podriamos querer escribir tanto un cliente personalizado
como un servidor personalizado, como en las aplicaciones de tres capas. El sopor-
te de Delphi para este tipo de programa (que solia llamarse arquitectura MIDAS
IM~clclle-tierDistribz~tedApplicalion Services] y ahora se llama Datasnap) se
tratara mas adelante, en el capitulo 16.
El aumento de volumen de una aplicacion, es decir, la transferencia de datos
desde 10s archivos locales a un motor de base de datos de un servidor SQL, se
realiza normalmente por razones de rendimiento y para permitir el uso de grandes
cantidades de datos. Volviendo a1 ejemplo anterior, en un entorno clientelservi-
dor, la consulta utilizada para calcular el salario masimo la calcularia el RDBMS,
que enviaria solamente el resultado de vuelta a1 ordenador cliente, un unico nume-
ro. Con un servidor potente (corno una estacion Sun SparcStation multiprocesador),
el tiempo total necesario para el calculo del resultado seria minimo. No obstante,
esisten otras razones para escoger una arquitectura clientelservidor:

Ayuda a gestionar una gran cantidad de datos, porque no es aconse.jable


guardar cientos de megabytes en un archivo local.
Soporta la necesidad de acceso concurrente a 10s datos varios usuarios a1
mismo tiempo. Las bases de datos de 10s servidores SQL usan normalmen-
te el bloqueo optimista, una tecnica que permite que varios usuarios traba-
jen sobre 10s mismos datos y que retrasa el control de concurrencia hasta el
momento en que 10s usuarios envian de nuevo las actualizaciones.
Ofrece integridad de datos, control de transacciones, control de acceso,
soporte para copias de seguridad y otras prestaciones similares.
Soporta la programabilidad (la posibilidad de e.jecutar parte del codigo,
como procedimientos almacenados, disparadores, vistas de tablas y otras
tecnicas, en el servidor, reduciendo asi el trafico de red y la carga de
trabajo de 10s ordenadores cliente.
Una vez dicho esto, podemos empezar a centrarnos en tecnicas particulares
para la programacion clientelservidor. El objetivo general es distribuir correcta-
mente la carga de trabajo entre el cliente y el servidor, asi como reducir el ancho
de banda de red necesario para transportar la informacion.
La base de este enfoque es un buen diseiio de la base de datos, que implica
tanto la estructura de tablas como la validacion y restricciones apropiadas para
10s datos (las reglas de negocio). Obligar a la validacion de 10s datos en el servi-
dor resulta importante, porque la integridad de la base de datos es uno de 10s
objetivos claves de cualquier programa. Sin embargo, el lado del cliente deberia
incluir tambien validacion, para mejorar la interfaz de usuario y hacer que la
entrada y procesamiento de 10s datos sea mas amigable. Tiene poco sentido per-
mitir que el usuario introduzca datos no validos y reciba mas tarde un mensaje de
error por parte del servidor, cuando se puede impedir desde el comienzo una
entrada erronea.

Elementos del disefio de bases de datos


Aunque este libro trata sobre la programacion en Delphi, no sobre bases de
datos, es importante tratar algunos elementos de un diseiio de bases de datos
porque, si este es incorrect0 o complejo, tendriamos que escribir sentencias SQL
y codigo del servidor tremendamente complejos o escribir mucho codigo Delphi
para poder acceder a 10s datos, posiblemente incluso luchando contra el diseiio de
la clase T D a t a S e t .

Entidades y relaciones
La tecnica de diseiio de bases de datos relacionales clasica, basada en el mode-
lo entidad-relacion (E-R), implica tener una tabla para cada entidad que necesite-
mos representar en la base de datos, con un campo para cada elemento de datos
que necesitemos y un campo adicional para cada relacion uno a uno o uno a varios
con otra entidad (o tabla). En el caso de las relaciones varios a varios, sera
necesaria una tabla a parte.
Como ejemplo de relacion uno a uno, supongamos una tabla que represente un
curso universitario. Tendria un campo para cada elemento de datos importante
(nombre y descripcion, sala en la que se impartira, etc.) ademas de un campo
unico en el que se indique el profesor. Los datos del profesor, de hecho, no se
deberian almacenar junto con 10s datos del curso, sin0 en una tabla independiente,
puesto que podria hacerse referencia a ellos desde cualquier otra parte.
El horario de cada curso puede incluir un numero no definido de horas de dias
distintos, por lo que no pueden aiiadirse dentro de la misma tabla que describe el
curso. En su lugar, esta informacion habra de colocarse en una tabla aparte, que
contenga todos 10s horarios, con un campo que haga referencia a la clase corres-
pondiente a cada horario. En una relacion uno a muchos como esta, "muchos"
registros de la tabla de horarios apuntan de nuevo al mismo registro ("uno") de la
tabla de cursos.
Se necesita una situacion mas compleja para almacenar informacion sobre que
estudiante recibira que clase. No se pueden listar 10s estudiantes directamente en
la tabla de cursos, porque su numero no es fijo, y las clases no se pueden guardar
en 10s datos sobre estudiantes por la misma razon. Asi, en una relacion varios a
varios de este tipo, la unica tecnica consiste en tener una tabla adicional que
represente la relacion y liste las referencias a estudiantes y cursos.

Reglas de normalizacion
Los principios clasicos sobre diseiio incluyen una serie de reglas de normaliza-
cion. El objetivo de estas reglas consiste en evitar la duplicacion de datos en las
bases de datos (no solo para ahorrar espacio, sino sobre todo para evitar producir
incoherencias de datos). Por ejemplo, no repetimos todos 10s datos de cliente en
cada pedido, sino que hacemos referencia a una entidad de cliente aparte. Asi,
ahorramos memoria y cuando cambien 10s datos del cliente (corno, por ejemplo,
un cambio de direccion), todos 10s pedidos de este cliente incluiran 10s nuevos
datos. Otras tablas que se relacionen con el mismo cliente tambien se actualizaran
automaticamente.
Las reglas de normalizacion implican el uso de codigos para valores repetidos
comunmente. Por ejemplo, si tenemos diferentes opciones de envio, no utilizare-
mos una descripcion basada en una cadena para dichas opciones en la tabla de
pedidos, sino un codigo numeric0 corto, proyectado sobre una descripcion en una
tabla de busqueda aparte. .
Esta ultima regla, que no deberia llevarse a1 extremo, ayuda a evitar tener que
agrupar un gran numero de tabla para cada consulta. Podemos tener en cuenta la
violacion de algunas de estas reglas de normalizacion en algunos casos (dejando
una breve descripcion del envio en la tabla de pedidos) o usar el programa cliente
para ofrecer la descripcion, con lo que acabariamos de nuevo con un diseiio de
base de datos formalmente incorrecto. Esta ultima opcion resulta practica solo
cuando usamos un entorno de desarrollo unico (corno Delphi) para acceder a la
base de datos.

De las claves primarias a 10s OID


En una base de datos relacional, 10s registros no se identifican mediante una
posicion fisica (corno en Paradox y otras bases de datos locales), sino solo por 10s
datos alojados en el propio registro. Normalmente, no necesitaremos todos 10s
campos para identificar un registro, sino solo un subconjunto, que conforma la
clave primaria. Si 10s campos que forman parte de la clave primaria deben identi-
ficar un registro individual, su valor habra de ser diferente para cada registro
posible de la tabla.
NOTA: Muchos servidores de base de datos d a d e n identificadores de re-
gistro internos a las tablas, per0 solo lo hacen de cara a optimizaciones
-
internas v tiene ~ o c oaue ver con el diseiio 16nico de una base de datos
relational. Ademas, estos identificadores internos fincionan de un modo
diferente en servidores SQL diferentes y podrian incluso cambiar entre las
distintas versiones. una huena razcin Dara no fiarse de ellos.

Las primeras encarnaciones de la teoria relacional dictaban el uso de claves


logicas, lo cual significa scleccionar uno o mas registros que indiquen una entidad
sin riesgo dc confusion. Normalmente, esto resulta mas facil de decir que de
realizar. Por ejemplo, 10s nombres de empresa no suelen ser unicos y ni siquiera el
nombre de la empresa y su ubicacion nos ofrecen una completa garantia. Ademas,
si una empresa cambia de nombre (algo que no es improbable, como Borland
puede enseiiarnos) o de ubicacion, y tenemos referencias a la empresa en otras
tablas, debemos cambiar tambien todas las referencias, con el riesgo de acabar
dejando rcferencias dcscolgadas.
Por este motivo, y tambien por razones de eficiencia (usar cadenas para refe-
rencias implica utilizar mucho espacio en tablas secundarias, en las que normal-
mcnte hay las referencias), las clavcs logicas se han ido reemplazando siempre
por claves fisicas o de sustitucion:
Claves fisicas: Se remiten a un solo campo dc la tabla que identifica un
elemento de un mod0 unico. Por ejemplo, cada persona en 10s EEUU tiene
un numcro de la Seguridad Social (SSN). pero casi todos 10s paises tienen
un idcntificador fiscal u otro numero asignado por el gobierno para identi-
ficar a cada persona. En el caso de las empresas, normalmente sucede lo
mismo. Aunque estos numero de identificacion son unicos, podrian cam-
biar en funcion del pais (creando problemas para las bases de datos de una
emprcsa que venda tambien sus productos en el estranjero) o incluso en un
mismo pais (ante nuevas leyes fiscales). Normalmente, tampoco son efica-
ces, puesto que podrian ser bastante largos (Italia, por ejemplo, usa un
codigo de 16 caracteres. letras y numeros para identificar a las personas).
Claves sustitutas: Un numero que identifica a cada registro, en forma de
codigos de cliente, numeros de pedido, etc. Estas claves sustitutas se usan
normalmente en el diseiio de bases de datos. Sin embargo, en muchos ca-
sos, acabaran siendo identificadores logicos, con codigos de cliente en
todas partes (lo que no es una gran idea).

ADVERTENCIA: La situation se vuelve especialmente compleja cuando


estas claves sustitutas tienen tambikn un significado y ban de seguir nonnas
concretas. Por ejemplo, las empresas han de numerar las facturas con nu-
meros unicos y consecutivos, sln dejar huecos en Ia secuencia de numera-
cion. Esta situacion resulta extremadamente compleja de controlar en un
programa, si tenemos en cuenta que solo la base de datos puede establece~
--A^-
esios numerus C;unseCuiivos_'--:---
-_'_----- -__---I- --_I---- -I-*--
unlws cuanuo - 1la- -:-
envlamos uaios nuevos a rms-
ma. A1 mismo tiempo, necesitamos identificar el registro antes de enviarlo a
la base de datos, si no, no seremos capaces de ir de nuevo a buscarlo.
Algunos ejemplos practicos del capitulo 15 mostrarhn como solucionar esta
situacion.

OID hasta el extremo


Una ampliacion del uso de tas claves sustitutas es el uso de un unico
identificador de objeto (Object Identifier, OID). Un OID es un numero o
una cadena con una secuencia de numeros y digitos. Se aiiade a cada regis-
tro de cada tabla que represente una entidad (y a veces, incluso, a registros
de tablas que representan relaciones). A diferencia de 10s codigos de clien-
te. numeros de factura. numeros de la sewridad social o numeros de uedi-
L,

do, 10s OID son totalmente aleatorios, sin ninguna norma de secuenciacion
y nunca resultan visibles pita el usuario final. Esto significa que podemos
2 - -1
seguir usanao _.._A:L..A-_
claves susc~iulas I_: --_-_-_- _--_L..___L__-I_
(SI nuttscra ttmpresa ttsla acosiumuraaa a
- _ A 1 -

ellas) junto con 10s OID, per0 todas las referencias externas a la tabla se
basarin en 10s OID.
Otra norma comun sugerida por 10s promotores de esta tecnica (que forma
parte de las teorias que apoyan la proyeccion relacional a objetos) es el uso
de identificadores unicos en todo e! sistema. Si tenemos una tabla de empre-
sas clientes v una tabla de em~leados. , .vodriamos . -
ureguntarnos uor auk
deberiamos usar un identificador unico para datos tan diversos. La razon es
que, si lo bacemos asi, podremos vender productos a un empleado sin tener
----A:- 1 - :-r
que r e p e ~ ~ :L- --L--
larmrorrnauon -a - - - I - - - I -
swore GI ernpleauo en -- la
1 - .-LI- 1- -I:--&--
raola ue L--:--
cuenws, nawen-
do sencillamente referencia at empleado en nuestro pedido o factura. AI-
guien identificado mediante un OID realiza un pedido y dicbo OID puede
remitir a varias tablas distintas. Usar identificadores OID y proyeccion
relacional a objetos es un elemento avanzado del disefio de aplicaciones de
bases de datos en Delphi. Es aconsejable investigar mhs acerca de este tema
antes de embarcarse en un proyecto medio o grande de Delphi porque 10s
beneficios pueden ser importantes (despues de una inversion en estudiar
este enfoque y crear un codigo bhico de soporte).

Claves externas e integridad referencial


Las claves que identifican un registro (sea cual sea su tipo) pueden usarse
como claves esternas en otras tablas, por ejemplo, para representar diversos tipos
de relaciones como las ya mencionadas. Todos 10s servidores SQL pueden com-
probar estas referencias externas, por lo que no podemos hacer referencia a un
registro no esistente de otra tabla. Estas restricciones de integridad referencial se
expresan cuando creamos una tabla.
Ademas de no poder aiiadir referencias a registros no esistentes, normalmente
se nos impide borrar un registro si existen referencias esternas a1 mismo. Algunos
servidores SQL van mas alla: cuando borramos un registro, en lugar de denegar-
nos la realization de dicha operacion, pueden borrar automaticamente todos 10s
registros de otras tablas que hagan referencia a el.

Mas restricciones
Ademas de la exclusividad de las claves primarias y de las restricciones
referenciales, generalmente podemos usar la base de datos para imponer mas
normas de validez para 10s datos. Podemos pedir que columnas concretas (como
las que se refieren a un identificador fiscal o a un numero de pedido de compra)
incluyan so10 valores unicos. Podemos imponer la exclusividad de 10s valores
para varias columnas, por e.jemplo, para indicar que no podemos dar clases en la
misma aula a la misma hora.
Por lo general, se pueden expresar normas sencillas para imponer restricciones
sobre una tabla, mientras que para las normas mas complejas, hay que ejecutar
procedimientos almacenados activados mediante disparadores (cada vez que 10s
datos cambian, por ejemplo, o que hay datos nuevos).
Una vez mas, hay muchos temas relacionados con un correct0 diseiio de bases
de datos. per0 10s elementos comentados en esta seccion serviran para proporcio-
nar un bucn punto de partida.
-
NOTA: Para conseguir mas infonnacion sobre el lenguaje de definicion de
datos (DDL)de SQL y el lenguaje de manipulacibn de datos (DML),
consultense las referencias del anexo D en el CD-ROM.

Cursores unidireccionales
En bases de datos locales, las tablas son archivos secuenciales que tienen un
orden o bien fisico o definido por un indice. Por el contrario, 10s servidores SQL
funcionan en conjuntos de datos logicos, no relacionados mediante un orden fisi-
co. Un servidor de bases de datos relacionales controla datos segun el modelo
relacional, un modelo matematico basado en una teoria fija.
Lo importante en este ambito cs que en una base de datos relacional, 10s regis-
tros (a veces llamados tuplas) de una tabla no se identifican por su posicion sino
exclusivamente mediante una clave primaria. basada en uno o mas canipos. Cuando
hemos obtenido un con.junto de registros, el servidor aiiade a cada uno de ellos
una referencia a1 siguiente, lo cual hace que sea mas rapido ir desde un registro a1
siguiente, per0 muy lento volver al registro anterior. Por esta razon, se suele decir
que un RDBMS usa un cursor unidireccional. Conectar una tabla o consulta de
este tipo a un control DBGrid resulta practicamente imposible, puesto que se
ralentizarian terriblemente las busquedas hacia atras en la cuadricula.
Algunos motores de bases de datos guardan en una cache 10s datos ya conse-
guidos, para soportal- una navegacion completamente bidireccional. En la arqui-
tectura de Delphi. es el componente C 1i e n t D a t a S e t el que se encarga de esta
tarea, o algun otro conjunto dc datos con almacenamicnto local de datos. Veremos
este proccso mas detenidamente mas adelante. cuando nos centremos en dbEspress
y cl componente C l i e n t D a t a S e t .

NOTA: El caso de un DBGrid utilizado para explorar una tabla completa


es comun en programas locales, per0 normalmente deberia evitarse en un
entorno cliente/servidor. Es mejor filtrar solo parte de 10s registros y so10
aquellos campc1s que nos interesen. Si se necesitara ver una lista de nom-
.
bres, es aconsejable conseguir primer0 aquellos que empiecen con la letra
1
A, luego 10s que comiencen con la B, etc. 0 pedir a1 usuario la inicial del
I -

nombre.
-

Si retroceder pucdc originar problemas, tengamos cn cuenta que saltar a1 ulti-


mo registro de una tabla puede resultar incluso peor. Normalmente esta operacion
conlleva la estraccion de todos 10s registros. En el caso de la propiedad
R e c o r d c o u n t dc 10s conjuntos de datos, tenemos una situation similar. Calcu-
lar el numero dc rcgistros implica normalmente llevarlos todos a1 ordenador clien-
tc. Esta es la razon por la que el indicador de la barra dc desplazamiento vertical
del DBGrid funciona en el caso de una tabla local pero no de una remota. Si
necesitamos conocer el niimero de registros. hay que e.jecutar una consulta aparte
para permitir a1 servidor (y no a1 cliente) que la calcule. Por ejemplo, podemos
cuantos registros se seleccionaran de la tabla EMPLOYEE si estamos interesados
en que aquellos tengan un calnpo de salario (salary) mayor de 50.000:
select count ( * )
f r o m Employee
w h e r e Salary > 50000

TRUCO: Usar la instruction SQL c o u n t ( 1 resulta muy comodo para


+

calcular el numero de registros devueltos por la consulta. En lugar de la


mascara *, podriamos haber usado el nombre de un campo especifico, como
en c o u n t ( F i r s t Name ) , posiblemente combinado con d i s t i n c t o
a l l . para contar s%lo registros con valores diferentes para el campo o
todos 10s registros que tengan un valor no nulo.
Introduccion a InterBase
A pesar de su reducida cuota de mercado, InterBase es un potente RDBMS. En
esta seccion, presentaremos las principales caracteristicas tecnicas de InterBase
sin entrar en demasiado detalle (ya que este libro trata sobre Delphi). Lamenta-
blemente, actualmente hay pocos titulos publicados sobre InterBase. La mayor
parte del material disponible es la documentacion que acompaiia a1 product0 o
que se encuentra en algunos sitios Web dedicados a ello (se puede comenzar la
busqueda en www.borland.com/interbase y www.ibphoenix.com).
InterBase se construyo desde el principio con una arquitectura moderna y
robusta. Su autor original, Jim Starkey, invent6 una arquitectura para manejar la
concurrencia y las transacciones sin imponer bloqueos fisicos sobre partes de las
tablas, alguno que otros servidores rnuy conocidos hoy en dia apenas hacen. La
arquitectura de InterBase se llama Multi-Generation Architecture (MGA); ges-
tiona 10s accesos concurrentes a 10s mismos datos por parte de varios usuarios,
que pueden modificar 10s registros sin afectar a1 mod0 en que otros usuarios
concurrentes contemplan la base de datos.
Este enfoque se proyecta con naturalidad sobre el mod0 de aislamiento de
transacciones de lectura repetida, en el que el usuario que utiliza una transaccion
sigue viendo 10s mismos datos sin importar que se produzcan y confirmen cam-
bios por parte de otros usuario. Tecnicamente, el servidor maneja la situacion
manteniendo una version diferente de cada registro a1 que se accede para cada
transaccion abierta. Incluso aunque este enfoque (que tambien se llama versionado)
puede llevar a un gran consumo de memoria, evita la mayoria de 10s bloqueos
fisicos sobre tablas y hace que el sistema resulte mucho mas robusto en caso de
problemas. MGA tambien empuja hacia un modelo de programacion rnuy claro,
la lectura repetible, que otros servidores SQL rnuy populares no soportan sin
perder la mayor parte de su rendimiento. Ademas de MGA como corazon de
InterBase, el servidor tiene muchas otras ventajas tecnicas:
Una ocupacion en memoria reducida: Hace que InterBase resulte el can-
didato ideal para ejecutarse directamente sobre ordenadores de cliente,
incluidos portatiles. El espacio de disco duro necesario para InterBase en
una instalacion minima esta por debajo de 10s 10 MB, y sus necesidades de
memoria son tambien rnuy reducidas.
Un buen rendimiento con grandes cantidades de datos.
Esta disponible en muchas plataformas distintas (como las versiones de
32 bits de Windows, Solaris y Linux), con versiones completamente com-
patibles. Por eso el servidor es escalable desde sistemas rnuy pequeiios a
sistemas gigantescos sin ninguna diferencia digna de mencion.
Un buen historial: Porque InterBase se esta usando desde hace 15 aiios,
con rnuy pocos problemas.
Un lenguaje compatible con el estandar SQL de ANSI.
Prestaciones de programacion avanzada: Como disparadores de posi-
cion, procedimientos almacenados seleccionables, vistas que se pueden
actualizar, excepciones, eventos; generadores, etc.
Una instalacion y adrninistracion muy sencilla: Con pocos dolores de
cabeza administrativos.

Una breve historia de InterBase


Jim Starkey escribio InterBase para su empresa, Groton Database Systems
.
(de donde procede la extension gds que sigue usandose para archivos de
InterBase). La empresa fue comprada por Ashton-Tate, que fue comprada
a su vez por Borland. Borland gestiono directamente InterBase durante un
tiempo y despuis creo una empresa subsidiaria, que mhs tarde volvio a
absorberse en la compaiiia nodriza. Desde Delphi 1, se ha distribuido siem-
pre una copia de evaluacion de InterBase con ia herramienta de desarrollo,
difundiendo el servidor de bases de datos entre 10s desarrolladores. Aunque

puiiado de empresas, InterBase ha sido escogido por varias empresas im-


portantes, desde Ericsson a1 Departamento de Defensa de 10s Estados Uni-
--.-,
dnc --- -- mercndns
decde -- cnmhin
-"-- ----- de -------- -a sistcmac de
-- hanca
---- rlnmkctica
--------'I-I-- ------.-----. Entre- --.-
lnc
sucesos mas recientes se incluyen el anuncio de InterBase 6 como una base
de datos de codigo abierto (en diciembre de 1999), la publicacion efectiva
del codigo fuente para la comunidad (en julio de 2000) y la publicacion de
la version certificada oficial de LnterBase 6 por Borland (en marzo de 200 1).
Entre estos eventos se han producido anuncios de la derivacibn de una
emmesa inde~endienteDara " w . .
nestionar 10s neaocios de consultoria v soDorte
ademas de la base de datos de codigo abierto. Un grupo de antiguos
desarrolladores y jefes de proyecto de InterBase (que dejaron Borland) for-
maron rmn nr~n- o
--:-- f \ - - - 1- :>--
:L-L---:-- ---.-
e n ~ x1www.mpnoenlx.corn) con la laea ue>- -c----- ---- --
orrecer soporre a-
10s usuarios de InterBase. A1 mismo tiempo, grupos independientes de ex-
pertos en InterBase comenzaron el proyecto de c6digo abierto Firebird para
extender mas alla InterBase. El proyecto se hospeda en SourceForge en la
direction sourceforge.net/projects/firebird/.Durante algun tiempo,
SourceForge tambien ha hospedado un proyecto de c6digo abierto de
Borland, pero mas tarde la empresa anuncio que continuaria soportando
unicamente la version propietaria, abandonando su esfuerzo de c6digo abier-
to. Asi, el escenario queda mas claro. Si se quiere una versi6n con una
1: ---- :- *-->:-:---1 f
1lr;encla rraumonal [que ----- una pequerra
cuesm L- x- 1- 1-
parre ue
--A- ----- I--
w que cues~anIUS
--.A A--

servidores SQL profesionales m h competitivos), conviene seguir con


Borland; per0 si se prefiere un modelo de cbdigo abierto, totalmente gratui-
to, lo mejor es consultar el proyecto Firebird (y contratar en ultima instan-
cia el soporte profesional de IBPhoenix).
Uso de IBConsole
En las ultimas versiones de InterBase, se podian usar dos herramientas princi-
pales para interactuar directamente con el programa: la aplicacion Server Mana-
ger, que podia usarse para administrar tanto un servidor local como uno remoto,
y Windows Interactive SQL (WISQL). La version 6 incluye una aplicacion final
mucho mas potente, llamada IBConsole. Se trata de un programa para Window
muy completo (creado con Dclphi) que permite administrar, configurar. probar y
consultar a1 servidor InterBase, tanto en local como en remoto.
IBConsole es un sistema completo y sencillo para gestionar servidores InterBase
y sus bases de datos. Puede usarse para analizar 10s detalles de la estructura de la
base de datos, modificarla, consultar datos (lo que puede ser muy util para desa-
rrollar las consultas que se quieran incluir en el programa), hacer copias de segu-
ridad y recuperar la base de datos, y llevar a cabo otras tareas administrativas.
Como muestra la figura 14.1, IBConsole permite administrar varios servido-
res y bases de datos, todos ello en un simple arb01 de configuracion. Se puede
solicitar informacion general sobre la base de datos y mostrar sus entidades (ta-
blas. dominios, procedimientos almacenados, disparadores y todo lo demas), ac-
cediendo a 10s detalles de cada una de ellas.
Tambien pueden crearse nuevas bases de datos y configurarlas, hacer copias
de respaldo de 10s archivos, actualizar las definiciones, consultar lo que sucedc,
quien se encuentra conectado, y cosas asi.

mole V w Sewer JP

839 ' * % I - - -- .- -- - -- -

3 InterBaseServels Sctmn _ _Descrptm I

- "J LmalServer Drsconnect D~sconnrctfrom the curent database


38 Databases Propeltles Show database properhes
-1 '$ Database Stal~st~cs D~splaydatabase std~st~cs
@ Domalns Shutdown ShutdownIhe database
Tables Sweep Perform a database sweep
aV~ews
Transact~on
Recovery Recovel lhmbo transactrons
tbStored Procedures
V~ewMetadata V~ewDatabase Metadata
fx External Functions
Database Restart Restart a database
% Genelators
Drop Database Dlop the current database
0 Except~ons
DatabaseBackup Backup an InlerBase database
8 Blob Fltels
ConnectedUsers VIM a llsl d users curlently connectedto the server
@ Roles
IBWintech RestoreDatabase Restorean InterBwe database
@ WlNTECH GDB
a Backup
EB base
3 Sewer Log
@ USQS
3 wmlech-sewer

Figura 14.1. IBConsole permite administrar desde un unico ordenador bases de


datos de InterBase, hospedadas en varios servidores.
La aplicacion IBConsole permite abrir varias ventanas para ver information
detallada, como la ventana de tablas que muestra la figura 14.2. En esta ventana,
se pueden ver listas de las propiedades claves de cada tabla (columnas,
disparadores, restricciones e indices), ver 10s metadatos en bruto (la definition
SQL de la tabla), 10s permisos de acceso, very modificar 10s datos, y analizar las
dependencias de la tabla.
Hay disponibles ventanas parecidas para cada una de las demas entidades que
se pueden definir en una base de datos.

I EMP-NO
FIRST-NAME
LAST NAME
[EMPNO] SMALLINT
[FIRSTNAME] VARCHAR(151
ILASTNAMEI VARCHARlZOl
Yes

IHIRE-DATE
DEPT-NO
JOB CODE
JOB~GRADE
JOB-COUNTRY
TIMESTAMP
[DEPTNO) CHAR01
IJOBCODEI VARCHARISI
~JOBGRADEJSMALLINT.
(COUNTRYNAME] VARCHARIlS]
DEFAULT 'NOW No
No
No
No
No
SALARY [SALARY] NUMERIC[lS. 21 DEFAULT 0 No
FULL-NAME VARCHAR Yes

-- -- - ---
IC \ \exam&s\~atabese\e& adb Tables

Figura 14.2. IBConsole puede abrir ventanas independientes para rnostrar 10s
detalles de cada entidad (en este caso, una tabla).

IBConsole incluye una version mejorada de la aplicacion Windows Interactive


SQL original (vease figura 14.3). Se puede escribir una sentencia SQL en la parte
superior de la ventana (lamentablemente sin ninguna ayuda por parte de la herra-
mienta) y ejecutar a continuacion la consulta SQL. Como resultado, se veran 10s
datos, per0 tambien la planificacion de acceso utilizada por la base de datos (que
un experto podria usar para determinar la eficiencia de la consulta) y estadisticas
sobre la operacion realizada por el servidor.
Esto ha sido una breve descripcion de IBConsole, que es una potente herra-
mienta (y la unica que incluye Borland junto con el servidor, ademas de herra-
mientas en linea de comandos). Aun asi, IBConsole no es la herramienta mas
completa de su propia categoria.
Otras aplicaciones de administracion de InterBase de terceros son mas poten-
tes, aunque no tan estables o amigables. Algunas herramientas de InterBase son
programas shareware, y otros son gratuitos. Dos ejemplos, entre otros muchos,
son InterBase Workbench (www.upscene.com) e IB-WISQL (creada con y parte
de InterBase Objects, www.ibobjects.com).
- . .- . . .. .- . - . --
l a 7 - @ - 8 1 B I h a I k % l f i=:. z .. .
1
. . .. ~ .!
k e l e c t last-name, hire-date, salary A

f r m employee
where salary > l O O O I 2 0

Figura 14.3. La ventana Interactive SQL de IBConsole perrnite probar consultas que
se planee incluir en programas Delphi.

Programacion de servidor en InterBase


A1 comienzo de este capitulo, hemos subrayado el hecho de que uno de 10s
objetivos de la programacion clientelservidor (y probablemente uno de sus pro-
blemas) sea la division de la carga de trabajo entre 10s ordenadores implicados.
Cuando se activan sentencias SQL desde el cliente, es el servidor el que se encar-
ga de la mayor parte del trabajo. Sin embargo, no deberian tratarse de usar sen-
tencias select que devuelvan un gran conjunto de resultados, para no saturar la
red. Ademas de aceptar DDL (Data Definition Language, lenguaje de definition
de datos) y DML (Data Manipulation Language, lenguaje de manipulacion de
datos), la mayoria de 10s servidores RDBMS permiten crear directamente rutinas
en el servidor empleando comandos SQL estandar ademas de las extensiones
propias del servidor (que no suelen ser faciles de adaptar). Estas rutinas suelen
ser de dos tipos: procedimientos almacenados y disparadores.

Procedimientos almacenados
Los procedimientos almacenados son como las funciones globales de una uni-
dad Delphi, y deben llamarse explicitamente desde el lado del cliente. Los pro-
cedimientos almacenados suelen utilizarse para definir rutinas para el
mantenimiento de 10s datos, para agrupar secuencias de operaciones necesarias
en distintas situaciones, o para contener complejas sentencias select.
Al igual que 10s procedimientos de Delphi, 10s procedimientos almacenados
pueden tener uno o mas parametros con tipo. Por contra, pueden tener mas de un
valor de retorno. Como alternativa a devolver un valor, un procedimiento almace-
nado tambien puede devolver un conjunto de resultados (el resultado de una sen-
tencia s e 1 e ct interna o un conjunto fabricado de forma personalizada).
El siguiente fragment0 de codigo es un procedimiento almacenado escrito para
InterBase: recibe una fecha como entrada y calcula el salario mas alto entre todos
10s empleados contratados en esa fecha:
create procedure MaxSalOfTheDay (ofday date)
returns (maxsal decimal ( 8 , 2 ) ) as
begin
s e l e c t max (salary)
from employee
where hiredate = :ofday
i n t o :maxsal;
end

Hay que prestar atencion a1 uso de la clausula into, que indica a1 servidor
que guarde el resultado de las sentencia select en el valor de retorno maxsal .
Para modificar o eliminar un procedimiento almacenado, se pueden usar mas
adelante 10s comandos alter procedure y drop procedure.
Si nos fijamos en este procedimiento almacenado, podriamos preguntarnos
cual es la ventaja en comparacion con la ejecucion de una consulta similar activa-
da en el cliente.
La diferencia entre 10s dos enfoques no esta en el resultado conseguido, sin0 en
su velocidad. Un procedimiento almacenado se compila en el servidor en una
notacion intermedia mas rapida durante su creacion, y el servidor escoge en ese
momento la estrategia a utilizar para acceder a 10s datos. En cambio, una consul-
ta se compila cada vez que se envia la peticion a1 servidor. Por este motivo, un
procedimiento almacenado puede sustituir a una consulta muy compleja, siempre
que no cambie muy a menudo.
Desde Delphi, se puede activar un procedimiento almacenado con el siguiente
codigo SQL:
select
from MaxSalOfTheDay ( 'Ol/Ol/ZOO3 ')

Disparadores (y generadores)
Los disparadores se comportan mas o menos como 10s eventos en Delphi, y se
activan automaticamente cuando se produce un evento determinado. Los
disparadores pueden tener codigo especifico o llamar a procedimientos almacena-
dos; en ambos casos, la ejecucion se realiza completamente en el servidor. Los
disparadores se usan para mantener la consistencia de 10s datos, comprobar 10s
datos nuevos de un mod0 mas complejo que el que permite la verificacion de
restricciones, y para automatizar efectos secundarios de algunas operaciones de
entrada (como crear un registro de 10s cambios de sueldo anteriores cuando se
modifica el sueldo actual).
Los disparadores pueden activarse mediante tres operaciones basicas de ac-
tualizacion de datos: i n s e r t , u p d a t e y d e l e t e . Cuando se crea un dispara-
dor, se indica si se deberia lanzar antes o despues de una de estas tres acciones.
Como ejemplo de un disparador, podemos utilizar un generador para crear un
indice unico en una tabla. Muchas tablas utilizan un indice unico como clave
primaria. InterBase no tiene un campo AutoInc (de incremento automatico). Ya
que varios clientes no pueden generar identificadores unicos, se puede delegar en
el servidor para que haga esto. Casi todos 10s servidores SQL ofrecen un contador
a1 que se puede llamar para solicitar un nuevo identificador, que deberia usarse
mas tarde para la tabla. InterBase llama a estos contadores automaticos genera-
dores, mientras que Oracle 10s llama secuencias: Este es el codigo de InterBase de
ejemplo:
c r e a t e generator cust-no-gen;
...
gen-id (cust-no-gen, 1);

La funcion g e n i d extrae el nuevo valor unico del generado pasado como


primer parimetro; el segundo parimetro indica de cuinto seri el incremento (en
este caso, de uno).
En este punto, se puede aiiadir un disparador a una tabla (un controlador
automatico para uno de 10s eventos de la tabla). Un disparador es como un con-
trolador de eventos del componente T a b l e , per0 se escribe en SQL y se ejecuta
en el servidor, no en el cliente. ~ s t es
e un ejemplo:
c r e a t e t r i g g e r set-cust-no f o r customers
before i n s e r t p o s i t i o n 0 a s
begin
new. cust-no = gen-id (cust-no-gen, 1);
end

Este disparador se define para la tabla de clientes y se activa cada vez que se
inserta un nuevo registro. El simbolo new se refiere a1 nuevo registro que se
introduce. La opcion p o s i t i o n indica el orden de ejecucion de varios
disparadores conectados a1 mismo evento. (Los disparadores con 10s valores mas
bajos se ejecutan en primer lugar.)
Dentro de un disparador, se pueden escribir sentencias DML que actualicen
tambien otras tablas, per0 hay que prestar atencion a las actualizaciones que
acaban volviendo a activar el disparador y crean una recursion sin fin. Despues se
puede modificar o inhabilitar el disparador, mediante una llamada a las senten-
cias a l t e r t r i g g e r o d r o p t r i g g e r .
Los disparadores se activan automaticamente para 10s eventos especificados.
Si hay que hacer muchos cambios en la base de datos utilizando operaciones de
lotes, la presencia de un disparador puede ralentizar el proceso. Si 10s datos de
entrada ya se han comprobado en relacion a su coherencia, se puede desactivar
temporalmente el disparador. Estas operaciones de lotes suelen codificarse en
procedimientos almacenados, per0 en general estos procedimientos no envian sen-
tencias DDL como las necesaria para desactivar y volver a activar el disparador.
En este caso, se puede definir una vista basada en un comando select * f r o m
table,creando asi un pseudonimo para la tabla. Despues se puede permitir que
el procedimiento almacenado realice el procesamiento de lotes sobre la tabla, y
aplique el disparador a la vista (que deberia utilizarse tambien por parte del
programa cliente).

La biblioteca dbExpress
Actualmente. el principal acceso a una base de datos de un servidor SQL en
Delphi lo proporciona la biblioteca dbEspress. Como mencionamos en el capitulo
13, no es la unica posibilidad, per0 es la mas usada. La biblioteca dbExpress se
present6 en Kylix y Delphi6, y permite acceder a varios servidores distintos
(IntcrBase. Oracle, DB2, MySql, Informix y ahora SQL Server de Microsoft).
Hemos ofrecido una vision general de dbExpress en comparacion con otras s o h -
ciones en cl capitulo anterior, asi que aqui nos saltaremos la presentation y nos
ccntraremos en elementos mas tecnicos.

NOTA: La inclusion de un controlador para SQL Server de Microsoft es la


actualizacion mas importante de dbExpress en Delphi 7. No se implementa
ofreciendo una interfaz con otras bibliotecas nativas del fabricante, como
otros controladores dbExpress, sino enlazando con el proveedor OLE DB
de Microsoft para SQL Server. (Hablaremos mas sobre 10s proveedores
OLE DB en el siguiente capitulo.)

Trabajo con cursores unidireccionales


El lema de dbExpress seria algo asi como "conseguir, per0 no almacenar". La
diferencia clave entre esta biblioteca y el BDE o ADO es que dbExpress solo
puede e-jecutarconsultas SQL y volver a buscar 10s resultados mediante un cursor
unidireccional. En el acceso a bases de datos "unidireccional", podemos mover-
nos de un registro a1 siguiente, per0 no podemos volver a1 registro anterior de un
con-juntode datos (a no ser que volvamos a abrir la consulta y recuperemos todos
10s registros anteriores, una operacion increiblemente lenta que bloquea dbExpress).
Eso se debe a que la biblioteca no almacena 10s datos que ha recuperado en una
cache local, sino que sol0 10s pasa del servidor de la base de datos a la aplicacion
que realiza la llamada.
El uso de un cursor unidireccional podria parecer que supone una restriccion,
y lo es. Ademas de tener problemas de navegacion, no podemos conectar una
cuadricula de base de datos a un conjunto de datos como este. Sin embargo, un
conjunto de datos unidireccional es bueno para 10s siguientes usos:
Podemos usar un conjunto de datos unidireccional para generar informes.
En un informe impreso, per0 tambien en una pagina HTML o en una
transformacion XML, nos movemos de un registro a otro, generamos la
salida y ya esta. No hay que volver a registros pasados y, por lo general, no
es necesaria la interaccion del usuario con 10s datos. Los conjuntos de
datos unidireccionales son probablemente la mejor opcion para las arqui-
tecturas Web y multicapa.
Podemos usar un conjunto de datos unidireccional para alimentar una cache
local, como la que ofrece un componente C l i e n t Dat aSe t . En este pun-
to, podemos conectar componentes visuales a 10s conjuntos de datos en
memoria y operar en ellos con todas las tecnicas estandar, como el uso de
cuadriculas visuales. Podemos navegar con libertad y editar 10s datos en la
memoria cache, per0 tambien controlarlos mucho mejor que con el BDE o
ADO.
Lo importante es que, en dichas circunstancias, evitar guardar el almacenamien-
to en cache del motor de base de datos ahorra en realidad tiempo y memoria. La
biblioteca no tiene que utilizar memoria adicional para la cache y no necesita perder
tiempo almacenando datos, duplicando la informacion. Durante 10s ultimos aiios,
muchos programadores han pasado las actualizaciones en cache basadas en el BDE
a1 componente C 1i e nt Data Se t , que ofrece mas flexibilidad en la gestion del
contenido de 10s datos y la actualizacion de la informacion que mantienen en memo-
ria. Sin embargo, usar un C l i e n t D a t a S e t sobre el BDE (o ADO), tiene el
riesgo de tener dos caches separadas, que desperdicia mucha memoria.
Otra ventaja del uso del componente C l i e n t D a t a S e t es que su cache so-
porta operaciones de edicion y las actualizaciones almacenadas se pueden aplicar
a1 servidor de base de datos original mediante el componente Dataset Provider.
Este componente puede generar las sentencias SQL adecuadas de actualizacion y
puede hacerlo de un mod0 mas flexible que el BDE (aunque ADO es tambien
bastante potente en este sentido). En general, el proveedor puede usar tambien un
conjunto de datos para las actualizaciones, per0 no resulta posible directamente
con 10s componentes de conjunto de datos dbExpress.

Plataformas y bases de datos


Un elemento clave de la biblioteca dbExpress es su disponibilidad tanto para
Windows como para Linux, en contraste con otros motores de bases de datos para
Delphi (BDE y ADO), que son solo para Windows. Sin embargo, algunos compo-
nentes especificos de bases de datos, como InterBase Express estan disponibles en
varias plataformas.
Cuando usamos dbEspress, se nos ofrece un marco de trabajo comun, que es
independiente del servidor de bases de datos SQL que planeamos usar. dbExpress
incluye controladores para MySQL, InterBase, Oracle, Informix, Microsoft SQL
Scrver e DB2 de IBM.

NOTAt Es posible escribir controladores personalizad~spara la dkquitec-


tura db&press. Esto estsl documentado con mayor detalle &el docmento
"dbExpms Draft Specification" publicado en el sitio Web dc Bodand
Community. Actualmente, este docurnento se encuentra en b&tp://'
, 1 O,224H,OO.htmI6~ & a b l k m ~ dpub
c ~ . b o r l a n d . c o m / a r t i c l e / O14 e
clan cncbntrarse controladores de terceros. Por ej&plo, c;uiste un ~ o n ~ o l p
dor ~ ~ iquet conecta o dbExpress y ODBC. Hay una Bsta cornpletar en el
articdd h ~ p : / / c m u n i t ~ . b o r l a n.comlarticle/0,14
d f 0,2 b 7 T ~ ~ ~ .

Problemas con las versiones de controladores


e inclusion de unidades
Tecnicamente, 10s controladores dbExpress se encuentran disponibles como
archivos DLL independientes que hay que desplegar junto con el programa. Asi
sucedia con Delphi 6 y sigue pasando lo mismo con Delphi 7. El problema es que
10s nombres de las DLL no han cambiado, por lo que si se instala una aplicacion
compilada en Delphi 7 sobre una maquina que tenga 10s controladores dbExpress
de Delphi 6, la aplicacion parecera funcionar, abrir una conexion a1 servidor, y
despues fallara cuando trate de obtener 10s datos. En este punto se vera el error
"SQL Error: Error mnppingfailed" ("Error SQL: fallo en la proyeccion"). No es
una buena pista para indicar que se trata de un problema de versiones en el
controlador dbExpress.
Para verificar este problema, podemos tratar de ver si la DLL tiene alguna
informacion de versiones (no era asi en 10s controladores de Delphi 6). Para que
las aplicaciones sean mas robustas, se puede realizar una comprobacion similar
en el codigo, accediendo a la informacion de la version usando las API de Windows
que tienen que ver con ello:
function GetDriverVersion ( s t r D r i v e r ~ a m e :string): Integer;
var
nInfoSize, nDetSize: DWord;
pVInf o, pDetail: Pointer;
begin
// p r e d e f i n i d o t h e d e f a u l t , s i no e x i s t e i n f o r m c i o n de
// v e r s i o n e s
Result : = 6 ;

/ / l e c t u r a de l a i n f o r m c i o n de v e r s i o n
nInfoSize : = GetFileVersionInfoSize (pChar(strDriverNarne),
nDetSize) ;
if nInfoSize > 0 then
begin
GetMern (pVInfo, nInfoSize) ;
try
GetFileVersionInfo (pChar(strDriverName), 0,
nInfoSize, pVInf o) ;
VerQueryValue (pVInfo, ' \ ' , pDetail, nDetSize) ;
Result : = HiWord
(TVSFixedFileInfo ( p D e t a i l A ).dwFileVersionMS) ;
finally
FreeMem (pVInfo) ;
end;
end;
end;

Este fragment0 de codigo procede del ejemplo DbxMulti ya comentado. El progra-


ma lo utiliza para lanzar una escepcion si se trata de una version incompatible:
if GetDriverVersion ( 'dbexpint. d l 1 ' ) <> 7 then
raise Exception.Create (
'Incompatible version o f the dbExpress driver
"dbexpint.dlln found') ;

Si se prueba a colocar el controlador que se encuentra en la carpeta bin de


Delphi 6 en la carpeta de la aplicacion, se vera el error. Habra que modificar esta
comprobacion adicional de seguridad para tener en cuenta versiones actualizadas
de 10s controladores o bibliotecas, pero este paso deberia ayudar a enviar 10s
problemas de instalacion que dbExpress trata de solucionar, antes de nada.
Existe tambien otra alternativa: se puede enlazar estaticamente el codigo de
10s controladores de dbEspress en la aplicacion. Para hacer esto, se incluye una
unidad determinada (como dbexpint .dcu o dbexpora .dcu) en el progra-
ma. indicandolo en una de las sentencias uses
. .- - - . - - - ..- -. .- - . - - ..
- - . -- ..
- - - - -
- - - - - .
- -

ADVERTENCIA:Junto con una de estas unidades es necesario incluir la


unidad MidasLib y enlazar el ckligo de MIDAS.DLLa1 programs. Si no se
hace esto, el enlazador de Delphi 7 mostrara un error intemo, que muestra
information sin mucho sentido. Hay que tener en cuenta que 10s controladores
dbExpress incrustados no funcionan correctamente con el conjunto intema-
cional de caracteres.

Los componentes dbExpress


Los componentes VCL utilizados para la interfaz de la biblioteca dbExpress
coordinan un grupo de componentes de conjuntos de datos mas unos cuantos
ausiliares. Para diferenciar estos componentes de otras familias de acceso a bases
de datos, 10s componentes llevan las letras SQL como prefijo, subrayando el
hecho de que se usan para acceder a servidores RDBMS.
Dichos componentes incluyen un componente de conesion de bases de datos,
algunos componentes de conjuntos de datos (uno generico; tres versiones especi-
ficas para tablas, consultas y procedimientos almacenados; y uno que encapsula
a1 componente C l i e n t D a t a S e t ) y una utilidad de seguimiento.

El componente SQLConnection
La clase TSQLConnecti o n hereda del componente TCustomConnection y
maneja conesiones a bases de datos, lo mismo que sus clases hermanas (10s com-
ponentes Database, ADOConnection e IBConnection).

TRUCO: A diferencia de otras familias de componentes, en dbExpress la


conexion es obligatoria. En cada uno de 10s componentes de conjuntos de
A,+," ,A ..,.Aa-*" ,",,,:c,..,
uarva, uw yvucauva G a p G u u L a I
AJ
,.-, ,.,A
U I I G ~ L Q I I I C ~ L GYUG
I...,,
u a a UG
~
A, A,"
UULVB
.uam,
,, "
:
,
a
SUIV

solo hacer referencia a una SQLConnection.

El componente de conexion utiliza la informacion disponible en 10s archivos


drivers.ini y connectionshi, que son 10s dos unicos archivos de configuration de
dbExpress (dichos archivos se guardan de manera predeterminada en Archivos
comunes\Borland Shared\DBEspress). El primero, drivers.ini, lista 10s
controladores dbEspress, uno para cada base de datos soportada. Para cada contro-
lador, existe un conjunto de parametros de conexion predefinidos. Por ejemplo, la
seccion InterBase es como sigue:
[Interbase]
GetDriverFunc=getSQLDriverINTERBASE
LibraryName=dbexpint.dll
VendorLib=GDS32.DLL
Blobsize=-l
CommitRetain=False
Database=database.gdb
Password=masterkey
RoleName=RoleName
ServerCharSet=ASCII
SQLDialect=l
Interbase T r a n s I s o l a t i o n = R e a d C o m r n i t e d
User-Name=s ysdba
WaitOnLocks=True

Los parametros indican la DLL del controlador de dbExpress (el valor


L i b r a r yName), la funci6n de entrada a usar ( G e t D r i v e r Func), la bibliote-
ca de cliente del fabricante y otros parametros especificos que dependen de la
base de datos. Si se lee todo el archivo drivers.ini, veremos que 10s parametros
son en realidad especificos de la base de datos. Algunos de estos parametros no
tendran mucho sentido a1 nivel del controlador (como la base de datos a la que
conectar), per0 la lista incluye todos 10s parametros disponibles, sin importar su
USO.
El archivo connections.ini ofrece la descripcion especifica de la base de datos.
Esta lista asocia configuraciones con un nombre, y se pueden escribir varios
datos de conexion para cada controlador de base de datos. La conexion describe
la base de datos fisica a la que queremos conectar. Como ejemplo, esta es la parte
de la definicion predefinida de IBLo ca 1:
[ IBLocal]
Blobsize=-1
CommitRetain=False
Database=C:\Archivos de programa\Archivos comunes\Borland
Shared\Data\employee.gdb
DriverName=Interbase
Password=masterkey
RoleName=RoleName
ServerCharSet=ASCII
SQLDialect=l
Interbase TransIsolation=ReadCommited
User-Name=s ysdba
WaitOnLocks=True

Como podemos ver a1 comparar 10s dos listados, este es un subconjunto de 10s
parametros del controlador. Cuando creamos una nueva conexion, el sistema co-
piara 10s parametros predefinidos del controlador. A continuacion, podemos edi-
tarlos para la conexion especifica (proporcionando, por ejemplo, un nombre de
base de datos correcto). Cada conexion se relaciona con el controlador para cada
uno de sus atributos clave, como indica la propiedad DriverName. Hay que
tener en cuenta que la base de datos a que se hace referencia aqui es el resultado
de una edicion, de acuerdo con 10s parametros usados en la mayoria de 10s ejem-
plos. Lo importante es recordar que estos archivos de inicializacion se usan solo
en tiempo de diseiio. Cuando seleccionamos un controlador o una conexion en
tiempo de diseiio, 10s valores de dichos archivos se copian en las propiedades
correspondientes del componente SQLConnec t io n, como en este ejemplo:
object SQLConnectionl: TSQLConnection
ConnectionName = ' IBLocal '
DriverName = ' Interbase'
GetDriverFunc = 'getSQLDriverINTERBASET
LibraryName = ' dbexpint . dll'
Loginprompt = False
Params.Strings = (
' Blobsize=-1 '
'CodtRetain=Palse'
'Database=c:\Archives d e programa \Archives
comunes\Borland Shared\Data\employee.gdb'
' DriverName=Interbasel
' Password=mas terkey'
' RoleName=RoleNamel
' ServerCharSe t = A S C I I 1
'SQLDialect=ll
'Interbase T r a n s I s o l a t i o n = R e a d C o d t e d '
' User-Name=sysdba '
' Wai tOnLocks=Truel)
VendorLib = 'GDS32.DLL'
end

En tiempo de ejecucion, nuestro programa confiara en las propiedades para


tener toda la informacion necesaria, por lo que no hay que desplegar 10s dos
archivos de configuracion junto con 10s programas. En teoria, 10s archivos seran
necesarios si queremos cambiar las propiedades Drive rName o Co nnec-
tionName en tiempo de ejecucion. Sin embargo, en caso de que queramos co-
nectar nuestro programa a una nueva base de datos, podemos establecer
directamente las propiedades oportunas.
Cuando aiiadimos un nuevo componente SQLConnection a una aplicacion,
podemos proceder de distintas formas. Podemos configurar un controlador utili-
zando la lista de valores disponible para la propiedad DriverName y, a conti-
nuacion, seleccionar una conexion predefinida, seleccionando uno de 10s valores
disponibles en la propiedad Connect ionName.Esta segunda lista se filtra ,

segun el controlador que ya hayamos seleccionado. Como alternativa, podemos


comenzar eligiendo directamente la propiedad Connect ionName,que en este
caso incluye la lista completa.
En lugar de conectar una conexion existente, podemos definir una nueva (o ver
10s datos de las conexiones existentes) haciendo doble clic sobre el componente
SQLConnection y lanzando el dbExpress Connection Editor (vease figura 14.4).
Este editor lista, a la izquierda, todas las conexiones predefinidas (para un
controlador especifico o todas ellas) y permite editar las propiedades de conexion
mediante la cuadricula que se encuentra a la derecha. Podemos emplear 10s boto-
nes de la barra de herramientas para aiiadir, borrar, dar un nuevo nombre y
probar conexiones y abrir la ventana dbExpress Drivers Settings de solo lectura,
que muestra tambien la figura 14.4.
Ademas de editar las configuraciones de conexion predefinidas, el dbExpress
Connection Editor tambien permite seleccionar una conexion para el componente
SQLConnection haciendo clic sobre el boton OK. Observe que si cambiamos
algunas configuraciones, 10s datos se escriben inmediatamente en 10s archivos de
configuracion: hacer clic sobre el boton Cancel no deshace 10s cambios.
Si queremos definir el acceso a una base de datos, lo mejor es editar las propie-
dades de conexion. De ese modo, cuando necesitamos acceder a la misma base de
datos desde otra aplicacion o desde otra conexion dentro de la misma aplicacion,
todo lo que hay que hacer es seleccionar la conexion. Sin embargo, dado que esta
operacion copia 10s datos de conexion, actualizar la conexion no refresca
automaticamente 10s valores de otros componentes SQLConnection que ha-
gan referencia a la conexion mencionada: tenemos que volver a seleccionar la
conexion a la que se refieren dichos componentes.

Dliva Nanw I Lb~wN ~ M I V& lbra19 1


082 DBEXPDBZ DLL &2cb dl1
lnlelbase d b e ~dYl GDS32 DLL
dbrrpnys dl LIBMYSOL dl1
O~acle dbexpmd 0 0 DLL

Figura 14.4. El dbExpress Connection Editor con el cuadro de dialogo dbExpress


Drivers Settings.

Lo que realmente importa para el componente SQLConnection es el valor de


sus propiedades. El controlador y las bibliotecas de fabricante se listan en propie-
dades que podemos cambiar libremente en tiempo de diseiio (aunque rara vez
querremos hacer esto), mientras la base de datos y otras configuraciones de co-
nexion especificas de bases de datos se listan en las propiedades Params.Se
trata de una lista de cadenas que incluye information como el nombre de la base
de datos, el nombre de usuario y la contraseiia, etc. En la practica, podriamos
configurar un componente SQLConnect ion configurando el controlador y asig-
nando directamente el nombre de la base de datos en la propiedad Params,
olvidandonos de la conexion predefinida. No estamos sugiriendo que sea la mejor
opcion, pero es una posibilidad: las conexiones predefinidas son practicas, per0
cuando cambian 10s datos aun sera necesario refrescar manualmente todos 10s
componentes SQLConnec t ion.
Para ser completos, tenemos que mencionar que existe una alternativa. Se
puede configurar la propiedad LoadParamsOnConnect para indicar que que-
remos refrescar 10s parametros del componente desde 10s archivos de inicializacion
cada vez que se abra la conexion. En este caso, un cambio en las conexiones
predefinidas se volvera a cargar cuando se abra la conexion, ya sea en tiempo de
diseiio o de ejecucion. En tiempo de diseiio, esta tecnica resulta util (tiene el
mismo efecto que volver a seleccionar la conexion); pero usarla en tiempo de
ejecucion significa que tambien habra que desplegar el archivo connections.ini, lo
que puede ser una buena idea o no, segun el entorno de despliegue.
La unica propiedad del componente SQLConnect ion que no esta relaciona-
do con el controlador ni las configuraciones de la base de datos es Loginprompt.
Definirla como False permite proporcionar una contraseiia que se salte el cua-
dro de dialog0 de peticion de entrada a1 sistema, tanto en tiempo de diseiio como
de ejecucion. Aunque es algo practico para el desarrollo, puede reducir la seguri-
dad del sistema. Por supuesto, deberia usarse tambien esta opcion para conexion
sin atencion, como las de un servidor Web.

Los cornponentes de conjuntos de datos


de dbExpress
La familia de componentes dbEspress proporciona cuatro componentes de
conjuntos de datos diferentes: un conjunto de datos generico, una tabla, una con-
sulta y un procedimiento almacenado. Los ultimos tres componentes se propor-
cionan por compatibilidad con 10s componentes BDE equivalentes y poseen
propiedades con nombres similares. Si no tenemos que adaptar el codigo actual,
deberiamos usar normalmente el componente SQLDataSet general, que permite
ejecutar una consulta per0 tambien acceder a una tabla o procedimiento almace-
nado.
El primer aspect0 importante que hay que resaltar es que todos estos conjuntos
de datos heredan de una nueva clase basica especial, TCustomSQLDataSet.
Esta y sus clases derivadas representan conjuntos de datos unidireccionales, con
las caracteristicas claves ya descritas. En la practica, esto significa que las opera-
ciones de navegacion se limitan a llamadas a First y Next,mientras que Prior,
Last,Locate,el uso de marcadores y todas las demas funciones de navegacion
estan desactivadas.

NOTA: Tecnicamente, algunas operaciones de desplazamiento liaman a la


funcion intema CheckBiDirectional'y puedm crear una exception.
C h e c k B i D i r e c t i o n a l se refiere a la propiedad publica
1sunidirectional de la clase TDataSet,que po&mos usar enulti-
mo termino en nuestro propio ckligo para desactivar las operaciones ilega-
les en conjuntos de datos unidireccionales.

Ademas de tener capacidades de navegacion limitadas, estos conjuntos de da-


tos no tienen soporte de edicion, por lo que muchos metodos y eventos comunes a
otros conjuntos de datos no estitn disponibles. Por ejemplo, no existe un evento
A f terEdit ni Bef orePos t . Como ya mencionamos, de 10s cuatro compo-
nentes de conjuntos de datos para dbExpress, el fundamental es TSQLDataSet,
que se puede usar tanto para obtener un conjunto de datos como para ejecutar una
orden. Estas dos alternativas se activan llamando al metodo Open (o definiendo
la propiedad Active como True) y llamando a1 metodo ExecSQL.
El componente SQLDataSet puede recuperar la tabla completa o usar una
consulta SQL o un procedimiento almacenado para leer un conjunto de datos o
enviar una orden. La propiedad CommandType establece uno de 10s tres modos
de acceso. Los posibles valores son ctQuery,ctStoredProc y ctTable,
que determinan el valor de la propiedad CommandText (y tambien el comporta-
miento del editor de la propiedad relacionada en el Object Inspector). En el caso
de una tabla o un procedimiento almacenado, la propiedad CommandText indi-
ca el nombre del elemento relacionado de la base de datos y el editor ofrece una
lista desplegable con 10s valores posibles. En el caso de una consulta, la propie-
dad CommandText almacena el testo de la orden SQL y el editor proporciona
algo de ayuda para crear la consulta SQL (en caso de que se trate de una sentencia
SELECT). Podemos ver el editor en la figura 14.5.

Add T d e lo SOL
- --- --

FIRST-NAME
LAST-NAME
PHONE-EX1
HIRE-DATE
OEPT-NO
JOB CODE
Add Fpld lo SQ1

Cancel 1 ~ d p _I
Figura 14.5. El CommandText Editor usado por el componente SQLDataSet para
consultas.

Cuando utilizamos una tabla, el componente creara una consulta SQL


automaticamente, puesto que dbExpress tiene como destino solo bases de datos
SQL. La consulta generada incluira todos 10s campos de la tabla, y si especifica-
mos la propiedad SortFieldNames,incluira una clausula sort by.
Los tres componentes de conjuntos de datos especificos tienen un comporta-
miento similar, pero especificamos la consulta SQL en la propiedad de lista de
cadenas SQL,el procedimiento almacenado en la propiedad S toredProcName
y el nombre de la tabla en la propiedad TableName (como en 10s tres compo-
nentes homologos del BDE).
El componente SimpleDataSet de Delphi 7
El componente SimpleDataSet es nuevo en Delphi 7. Se trata de una
combinacion de cuatro componentes ya existentes: SQLConnection, SQLDataSet,
DataSetProvider y ClientDataSet. El componente esta pensado para ser un asis-
tente (solo se necesita un componente en lugar de cuatro, que ademas deberian
estar conectados). El componente bisicamente es un conjunto de datos de cliente
con dos componentes compuestos (10s dos de dbExpress), ademas de un provee-
dor oculto. (El hecho de que el proveedor este oculto es extraiio, porque se crea
como un componente compuesto.)
El componente permite modificar las propiedades y eventos de 10s componen-
tes compuestos (ademas del proveedor) y sustituir la conexion interna por una
externa, de manera que varios conjuntos de datos compartan la misma conexion a
la base de datos. Ademas de esto, el componente tiene otras limitaciones, como la
dificultad de manipulation de 10s campos del conjunto de datos de acceso a datos
(que es importante para configurar campos claw y puede afectar a1 mod0 en que
se generan las actualizaciones) y la ausencia dc algunos eventos de pro\feedor.
Por eso, aparte de para algunas aplicaciones sencillas, no resulta recornendable
usar el componente SimpleDataSet .

NOTA: Delphi 6 incluia un componente aun mas simple y limitado, llama-


do SQLClientDataSet. Existen componentes parecidos para las tecnologias
de acceso a datos BDE e IBX. ~ h o t aorl land indica &e todos estos com-
ponentes son obsoletos. Sin embargo, Demos\Db\SQLClientDataSet con-
. - . . . - - . - - -.-
t~eneuna copla del componente ongmal, y se puede rnstalar en Delph17 por
motivos de compatibilidad. Pero se trata de un componente completamente
inutil.

El componente SQLMonitor
El ultimo componente del grupo dbExpress es SQLMonitor, utilizado para
registrar las solicitudes enviadas desde dbExpress al servidor de bases de datos.
Este componente de seguimiento permite ver las ordenes enviadas a la base de
datos y las respuestas recibidas a bajo nivel, haciendo un seguimiento del trafico
entre cliente y servidor a bajo nivel.

El tipo de campo de marca de tiempo


Junto con dbExpress, Delphi 6 introdujo el tipo de campo TSQLTime-
S t amp P i e Id,proyectado al tipo de d a b s de mama de tiempo (o timestamp)
que tienen muchos servidores SQL (incluido InterBase). Este tipo de datos
es una representacion basada en registros de una kcha u hora, y es bastante
distinta de la representacion de coma flotante utilizada por el tipo de datos
TDa teT i m e . Una matca o sello de tiempo se define asi:
TSQLTimeStamp -
padced record
Year : SmallInt;
Month : Word;
-. -- -
day : Pidrd;
Hour : Word;
Minute : Word;
Second : Word;
Fractions : Longword;
end;

Una marca de tiempo puede convertir automaticarnente valores de fecha y


L,--
u --AL-A
u~a
GSUU~I
----- --^-:^-l-A
1- rn-,-.-L-",l--
US~LUUU la yr u y ~ ~ u u u ud ~e I I I I E
AS
/--
\GIJ^-^-:-:A-
uyusluuu -1-
a la y ~ o -
piedad originaria A s SQLT imeSt amp). Tarnbien podemos realizar con-
versiones personalizadas y manipular aun mas las marcas de tiempo
utilizando las rutinas que ofrece la unidad SqlTirnSt, incluidas b c i o n e s
como DateTimeToSQLTimeStamp, S Q L T i m e S t a m p T o S t r y
VarSQLTimeStampCreate.

Algunos ejemplos de dbExpress


Despues de esta introduccion, veamos una demostracion que subraye las ca-
racteristicas claves de estos componentes y muestre como utilizar el ClientDataSet
para ofrecer soporte de edicion y almacenamiento en cache para 10s conjuntos de
datos unidireccionales. Mas adelante, mostraremos un ejemplo del uso nativo de
la consulta unidireccional, sin almacenamiento en cache ni soporte de edicion.
La aplicacion visual estandar basada en dbExpress usa esta serie de compo-
nentes:
El componente SQLConnection: Ofrece la conexion con la base de datos
y el controlador dbExpress adecuado.
El componente SQLDataSet: Enlaza con la conexion (mediante la pro-
piedad S Q L C o n n e c t i o n ) e indica que consulta SQL ejecutar o que ta-
bla abrir (usando las propiedades CommandT y p e y CommandTex t
mencionadas antes).
El componente Datasetprovider: Conectado con el conjunto de datos,
extrae 10s datos del SQLDataSet y puede generar las sentencias de actuali-
zacion SQL adecuadas.
El componente ClientDataSet: Lee del proveedor de datos y almacena
todos 10s datos (si su propiedad P a c k e t R e c o r d s esta definida como
-1) en memoria. Necesitaremos llamar a1 menos a su metodo A p p l y -
U p d a t e s para enviar las actualizaciones de vuelta a1 servidor de la base
de datos (a traves del proveedor).
El componente Datasource: Permite exponer 10s datos del ClientDataSet
a 10s controles data-aware.
Como mencionamos antes, esta situacion se puede simplificar usando el com-
ponente SimpleDataSet, que sustituye 10s dos conjuntos de datos y el proveedor
(y posiblemente incluso la conexion). El componente SimpleDataSet combina la
mayoria de las propiedades de 10s componentes a 10s que sustituye.

Uso de un componente unico o de varios


Para este primer ejemplo, colocaremos un componente SimpleDataSet en un
formulario y estableceremos el nombre de la conexion en su subcomponente
Connection. Configuraremos las propiedades CommandT y p e y ComrnandTex t
para especificar que datos obtener, y la propiedad P a c k e t R e c o r d s para indi-
car cuantos registros recuperar en cada bloque.
Estas son las propiedades clave de 10s componentes del ejemplo DbxSingle:
o b j e c t SimpleDataSetl: TSimpleDataSet
Connection.ConnectionName = ' I B L o c a l '
Connection.LoginPrompt = False
DataSet . C o m a n d T e x t = ' E M P L O Y E E '
D a t a S e t . C o m a n d T y p e = ctTable
end

Como alternativa, el ejemplo DbxMulti usa la toda secuencia de componentes:


o b j e c t SQLConnectionl: TSQLConnection
ConnectionName = ' I B L o c a l '
Loginprompt = False
end
o b j e c t SQLDataSetl: TSQLDataSet
SQLConnection = SQLConnectionl
CommandText = ' s e l e c t * f r o m EMPLOYEE'
end
o b j e c t DataSetProviderl: TDataSetProvider
DataSet = SQLDataSetl
end
o b j e c t ClientDataSetl: TClientDataSet
ProviderName = ' D at a S e t P r o v i d e r l l
end
o b j e c t DataSourcel: TDataSource
DataSet = ClientDataSetl
end

Ambos ejemplos tienen tambien algunos controles visuales: una cuadricula y


una barra de herramientas basados en la arquitectura del administrador de accio-
nes .

Aplicacion de actualizaciones
En cada ejemplo basado en una cache local, a1 igual que el ofrecido por 10s compo-
nentes C 1 i e n t DataSe t y S i m p l e D a t a S e t , es importante escribir 10s cambios
locales de nuevo en el servidor de la base de datos. Esto normalmente se realiza
llamando a1 metodo A p p l y U p d a t es . Podemos mantener 10s cambios en la cache
local durante algun tiempo y aplicar despues una serie de actualizaciones a la vez
o enviar cada cambio directamente. En estos dos ejemplos, hemos empleado la
ultima tecnica, adjuntado 10s siguientes controladores de eventos a 10s eventos
A f t e r P o s t (que se activa despues de las operaciones de edicion o insercion) y
A f t e r D e l e t e de 10s componentes C l i e n t D a t a S e t :
p r o c e d u r e TForml .Doupdate (Dataset: TDataSet) ;
begin
// a p l i c a i n m e d i a t a m e n t e 10s c a m b i o s l o c a l e s a la b a s e d e
// d a t o s
SQLClientDataSetl.ApplyUpdates(0);
end;

Si queremos aplicar todas las actualizaciones en un unico lote, podemos hacer-


lo asi cuando se cierre el formulario o finalice el programa, o dejar que un usuario
realice la operacion de actualizacion seleccionando una orden concreta, posible-
mente mediante la accion predefinida correspondiente que ofrece Delphi 7. Anali-
zaremos este enfoque con mas detalle cuando comentemos el soporte de cache de
actualizacion del componente C 1i e n t D a t as e t mas adelante.

Seguimiento de la conexion
Otra funcion que hemos aiiadido a 10s ejemplos DbxSingle y DbxMulti, es la
capacidad de seguimiento ofrecida por el componente S Q L M o n i t o r . En el ejem-
plo, el componente se activa a1 iniciarse el programa. En el ejemplo DbxSingle,
ya que el S i m p 1e D a t a S e t incluye la conexion, el monitor no puede conectarse
a ella en tiempo de diseiio, sino solo cuando arranque el programa:
p r o c e d u r e TForml.FormCreate(Sender: TObject);
begin
SQLMonitor1.SQLConnection : = SimpleDataSet1.Connection;
SQLMonitorl.Active : = True;
S i m p l e D a t a S e t l - A c t i v e : = True;
end

Cada vez que hay una cadena de seguimiento disponible, el componente activa
el evento O n T r a c e para permitirnos decidir si incluir la cadena en el registro. Si
el parametro L o g T r a c e de dicho evento es T r u e (el valor predefinido), el com-
ponente registra el mensaje en la lista de cadenas T r a c e L i s t y activa el evento
O n L o g T r a c e para indicar que se ha aiiadido una nueva cadena a1 registro.
El componente tambien puede almacenar automaticamente el registro en el
archivo indicado por su propiedad F i l e N a m e , per0 no hemos usado esta fun-
cion en el ejemplo. Todo lo que hemos hecho ha sido controlar el evento
O n L o g T r a c e , copiando todo el registro en el componente de memo mediante el
codigo siguiente (generando la salida que muestra la figura 14.6):
procedure TForml.SQLMonitorlLogTrace(Sender: TObject;
CBInfo: pSQLTRACEDesc; var LogTrace: Boolean) ;
begin
Memol.Lines : = SQLMonitor1.TraceList;
end;

INTERBASE ~sc-cwnnwt-relammg
INTERBASE ~sc-dsql-free-statement
INTERBASE . ISC-start-lransact~on
INTERBASE - ISC-dsql-allocale-statement
#updaleEMPLOYEE set
PHONE-EXT = 7
where
EMP-NO = 9 and
FIRST-NAME = 7 and
LAST-NAME = 7 and
PHONE EXT = 7 and
HIRE-D~TE = ? a i d

-
DEPT-NO = ? a n d
JOB-CODE ? and
JOB-GRADE = ? and
JOB-COUNTRY - ? and
SALARY = 7 and
FULL-NAME = ?

INTERBASE .isc-dsql-prepare
INTERBASE .isc-dsql-sql-inlo
INTERBASE .sc-van-inlegel
INTERBASE - kc-ds@-describe-bimd
INTERBASE - SQLD~alect= 1

Figura 14.6 Un registro de muestra conseguido por el SQLMonitor en el ejemplo


DbxSingle.

Control del codigo SQL de actualizacion


Si ejecutamos el programa DbxSingle y cambiamos, por ejemplo, el numero de
telefono de un empleado, el monitor de seguimiento registrara esa operacion de
actualizacion:
update EMPLOYEE set
PHONE-EXT = ?
where
EMP-NO = ? and
FIRST-NAME = ? and
LAST-NAME = ? and
PHONE-EXT = ? and
HIRE-DATE = ? and
DEPT-NO = ? and
JOB-CODE = ? and
JOB-GRADE = ? and
JOB-COUNTRY = ? and
SALARY = ? and
FULL-NAME = ?

A1 configurar las propiedades del SimpleDataSet no esiste un mod0 de


cambiar como se genera el codigo de actualizacion (lo que resulta peor que con el
componente SQLClientDataSet,que tenia la propiedad UpdateMode para
ajustar las sentencias de actualization).
En el ejemplo DbxMulti, puede usarse la propiedad UpdateMode del com-
ponente Datasetprovider configurando el valor como upwherechanged
o upWhereKeyOnly.En este caso se generaran las dos sentencias siguientes,
respectivamente:
update EMPLOYEE set
PHONE-EXT = ?
where
EMP-NO = ? and
PHONE-EXT = ?

update EMPLOYEE set


PHONE-EXT = ?
where
EMP-NO = ?

TRUCO: Este resultado es mejor que en Delphi 6 (sin aplicar 10s parches),
ya que esta operacibn causaba un error debido a que el campo clave no se
establecia correctamente.

Si queremos tener mas control sobre como se generan las sentencias de actua-
lizacion, necesitamos trabajar con 10s campos del conjunto de datos subyacente,
que estan disponibles tambien cuando se usa el componente aglutinador
SimpleDataSet (que tiene dos editores de campos, uno para el componente basico
ClientDataSet del que hereda y otro para el componente SQLDataSet que
incluye).
Hemos corregido de un mod0 parecido el ejemplo DbxMulti, despues de aiiadir
campos permanente para el componente SQLDataSet y modificar las opciones
del proveedor para incluir algunos de 10s campos en la clave o escluirlos de las
actualizaciones.

NOTA: Analizaremos este tipo de problema m b adelante de nuevo cuando


examinemos 10s detalles del componente ClientDataSet, el proveedor, el
resolutor y otros detalles tbcnicos m b adelante en este mismo capitulo y en
el capitulo 16.'

Acceso a metadatos de la base de datos con SetSchemalnfo


Todos 10s sistemas RDBMS usan tablas con fines especiales (denominadas
normalmente tablas de sistema) para almacenar metadatos, como la lista de ta-
blas, sus campos, indices y restricciones y cualquier otra informacion de sistema.
A1 igual que dbExpress ofrece una API unificada para trabajar con diferentes
servidores SQL, tambien ofrece una forma de acceso comun a metadatos. El
componente SQLDat aSet posee un metodo, Set SchemaInf o, que rellena el
conjunto de datos con informacion de sistema. Este metodo Set Schema Inf o
tiene tres parametros:
SchemaType: Indica el tipo de informacion solicitada y entre sus valores
se inchyen stTables, stSysTables, stProcedures, stcolumns
y stProcedureParams.
Schemaobject: Indica el objeto a1 que nos referimos, como el nombre de
la tabla para la que estamos solicitando las columnas.
SchemaPattern: Es un filtro que permite limitar nuestra solicitud a tablas,
columnas o procedimientos que comiencen con las letras dadas. Esto es
muy comodo si usamos prefijos para identificar grupos de elementos.
Por ejemplo, en el programa SchemaTest, un boton Tables lee dentro del conjun-
to de datos todas las tablas de la base de datos conectada:

El programa usa el habitual grupo de proveedor de conjunto de datos, conjunto


de datos cliente y componente de fuente de datos para mostrar 10s datos en una
cuadricula, como muestra la figura 14.7. Despues de obtener las tablas, podemos
scleccionar una fila en la cuadricula y hacer clic sobre el boton FieIds para ver
una lista de 10s campos de dicha tabla:
SQLDataSetl.SetSchemaInfo (stcolumns,
ClientDataSetl [ ' Table-Name' 1 , ' ');
C1ientDataSetl.Close;
ClientDataSetl.0pen;

Ademas de acceder a metadatos de bases de datos, dbEspress ofrece un mod0


de acceso a su propia informacion de configuracion, como 10s controladores ins-
talados y las conexiones configuradas. La unidad DbConnAdmin define una clase
TConnectionAdmin para dicho fin, pero el objetivo de este soporte esta limi-
tad0 a utilidades adicionales de dbExpress para desarrolladores (no se espera que
10s usuarios finales accedan a varias bases de datos de un mod0 totalmente dina-
mico).

TRUCO: El programa de ejemplo DbxExplorer incluido en Delphi mues-


tra como acceder tanto a 10s archivos de administration de dbExpress como
a la informacion esquemitica. Tarnbih puede consultarse el archivo de
ayuda con la leyenda "The structure of metadatu datasets", en la seccion
"Developingdatabase applications".
REWO I CATALOG-NAMEISMEMA-NME
1 <NIJLL> SYSDBA
( TABLE-NAME
COUNTRY
1TABLE-', -
SYSDBA CUSTOMER
SYSDBA DEPARTMENT
SYSDBA EMPLOYEE
SYSDBA EMPLOYEE_PROJECT
SYSDBA ITEMS
SYSDBA JOB
SYSDBA PHONE-LIST
SYSDBA PROJECT
SYSDBA PROJ-DEPT-BUDGET
SYSDBA SAIARY-HISTORY
SYSDBA SALES

Figura 14.7. El ejemplo SchemaTest permite ver las tablas de una base de datos y las
columnas de una tabla dada.

Una consulta parametrica


Cuando se necesitan versiones ligeramente distintas de la misma consulta SQL,
cn lugar de modificar el testo de la propia consulta cada vez, se puede escribir
una consulta con un parametro y modificar el valor del parametro. Por ejemplo, si
quisieramos que un usuario pudiera escoger 10s empleados de un pais determina-
do (usando la tabla employee), podriamos escribir la siguiente consulta parametrica:
select *
f r o m employee
w h e r e j ob-country = :country

En esta sentencia SQL, : country es un parametro. Puede establecerse su


tip0 de datos y valor inicial mediante el editor del conjunto de propiedades Params
del componente SQLDataSet. Cuando se abre el editor del conjunto Params
(como se muestra en la figura 14.8), se puede ver una lista de 10s parametros
definidos en la sentencia SQL. Puede fijarse el tip0 de datos y el valor inicial de
estos parametros en el Object Inspector. El formulario que muestra este progra-
ma, llamado ParQuery, utiliza un cuadro combinado para proporcionar todos 10s
valores disponibles para 10s parametros. En lugar de preparar 10s elementos del
cuadro combinado en tiempo de diseiio, se puede extraer el contenido disponible
de la misma tabla de la base de datos cuando arranque el programa. Esto se
realiza usando un segundo componente de consulta, con esta sentencia SQL:
select d i s t i n c t job-country
f r o m employee
'
I
PaamType
~tecision
ptlnpd
Io I

'All shown

Figura 14.8. Edicion del conjunto de parametros de un componente


de consulta.

Despucs de activar esta consulta, el programa analiza el conjunto de resulta-


dos, estraycndo todos 10s valores y aiiadicndolos a1 cuadro de lista:
p r o c e d u r e TQueryForm.FormCreate(Sender: TObject);
begin
SqlDataSet2.0pen;
while n o t SqlDataSet2.EOF d o
begin
ComboBoxl.Items.Add (SqlDataSet2.Fields [O].AsString);
SqlDataSet2.Next;
end;
ComboBoxl .Text : = CombBoxl. Items [ O ] ;
end ;

El usuario puede escoger un clement0 distinto en el cuadro combinado y haccr


despues clic sobre el boton Select (Buttonl) para modificar el parametro y
activar (o volvcr a activar) la consulta:
p r o c e d u r e TQueryForm.ButtoniClick(Sender: TObject);
begin
SqlDataSetl.Close;
C1ientDataSetl.Close;
Queryl. Params [O] .Value : = ListBoxl. Items
[Listboxl.ItemIndex];
SqlDataSetl.0pen;
ClientDataSetl.0pen;
end;

Este codigo muestra 10s cmpleados del pais seleccionado cn la DBGrid, tal y
como muestra la figura 14.9.
Como alternativa al uso de 10s elementos de la matriz Params por posicion,
podria considerarse el uso del metodo ParamByName;para evitar cualquier tipo
dc problema en caso de que la consulta se acabe modificando y 10s parametros
adoptcn un orden distinto.
-
England
I
EMP-NO IFIRST-NAMEIWT-NAME IPHONE-EXT HIRE-DAVE IDEPT-NO(J~
k 28 Ann Eennel 5 2/1/1991 120 Ad

- 36 Roger Reeves 6 412511991 120 Sa


- 37 W ~ l b Stanshy 7 4/25/1931 120 En-

Figura 14.9. El ejernplo ParQuery en tiernpo de ejecucion.

A1 utilizar consultas paramktricas, se suele poder reducir la cantidad de datos


que se dcsplazan desde el servidor a1 cliente y seguir usando una DBGrid y la
interfaz de usuario estandar habitual en las aplicaciones de bases de datos loca-
les.

TRUCO:Las consultas parametricas suelen emplearse tambien para con-


seguir arquitecturas maestroldetalle con consultas SQL, a1 menos esto es lo
que suele hacerse en Delphi. La propiedad Datasource del componente
SQLDataSet, sustituye automaticamente 10s valores de 10s parametros con
10s campos del conjunto de datos maestro que tengan el mismo nombre que
el p a r h e t r o .

Cuando basta una sola direccion: imprimir


datos
Hemos visto que uno de 10s elemcntos clave de la biblioteca dbExpress es que
dewclve conjuntos dc datos unidireccionales. Ademas, podemos usar el compo-
nente ClientDataSet (algunas de sus versiones) para almacenar 10s registros en
una cache local. Ahora es interesante comentar al menos un sencillo ejemplo en el
que todo lo que se nccesita es un conjunto de datos unidireccional.
Esto resulta muy frecuente para generar informes, es decir, para producir
informacion para cada rcgistro de forma continua sin necesidad de ningun otro
acceso a datos. Esta amplia categoria incluye la produccion de informes impresos
(mediantc un conjunto de componentes de informes o utilizando directamente la
impresora), el envio de datos a otras aplicaciones como Microsoft Excel o Word,
guardar datos en archivos (incluidos 10s formatos HTML y XML) y muchos mas.
No vamos a profundizar en HTML y XML, por lo que presentaremos un
ejemplo de impresion, nada demasiado atractivo ni basado en componentes de
generacion de informes, simplemente una forma dc generar un borrador de infor-
me en la pantalla y la impresora. Por esta razon, vamos a usar la tecnica mas
sencilla de Delphi para crear un resultado impreso: asignar un archivo a la impre-
sora mediante el procedimiento A s s i g n P r n de la RTL.
El ejemplo, denominado UniPrint, posee un componente unidireccional
SQLDataSet, vinculado a una conexion InterBase y basado en la siguiente
sentencia SQL, que une la tabla de empleados (employee) con la tabla de departa-
mentos (department) para mostrar el nombre del departamento en el que trabaja
cada empleado:
select d.DEPARTMENT, e.FULL-NAME, e.JOB-COUNTRY, e.HIRE-DATE
from EMPLOYEE e
inner join DEPARTMENT d on d.DEPT-NO = e.DEPT-NO

Para controlar la impresion, hemos escrito una rutina en cierto mod0 generica,
que requiere como parametros 10s datos que se van a imprimir, una barra de
progreso para la informacion de estado, la fuente de salida y el tamaiio de formato
maximo de cada campo. Toda la rutina usa el soporte de impresion de archivo y
da formato a cada campo con una cadena de tamaiio fijo, alineada a la izquierda
para producir un tipo de informe en columna. La llamada a la funcion Format
posee una cadena de formato parametrica creada de forma dinamica usando el
tamaiio del campo.
En el listado 14.1 se puede ver el codigo del metodo PrintOutDataSet
principal, que utiliza tres bloques try/ fina 11y anidados para liberar todos
10s recursos del mod0 correcto:
Listado 14.1. El rnetodo principal del ejemplo UniPrint.

procedure PrintOutDataSet (data: TDataSet;


progress: TProgressBar; Font: TFont; toFile: Boolean;
maxSize: Integer = 3 0 ) ;
var
PrintFile : TextFile;
I: Integer;
sizeStr: string;
oldFont: TFontRecall;
begin
// a s i g n a l a s a l i d a a l a i m p r e s o r a o a u n a r c h i v o
if toFile then
begin
SelectDirectory ( ' C h o o s e a f o l d e r ' , ' ', strDir) ;
AssignFile (PrintFile,
I n c l u d e T r a i l i n g ~ a t h D e l i m i t e r(strDir) + ' o u t p u t . t x t ' ) ;
end
else
AssignPrn (PrintFile);
// a s i g n a l a i m p r e s o r a a u n a r c h i v o
AssignPrn (PrintFile);
Rewrite (PrintFile);

// d e f i n e l a f u e n t e y m a n t i e n e l a o r i g i n a l
oldFont : = TFontRecall-Create (Printer.Canvas.Font);
try
Printer.Canvas.Font : = Font;
try
data.Open;
try
// imprime el encabezamiento (nombres d e campo) en
// negrita
Printer.Canvas.Font.Sty1e : = [fsBold];
f o r I : = 0 t o data. Fieldcount - 1 do
begin
sizeStr : = IntToStr (min
(data.Fields [i] .Displaywidth, maxSize) ) ;
Write (PrintFile, Format ( ' B - ' + sizeStr + 's',
[data.Fields [i] . FieldName] ) ) ;
end;
Writeln (PrintFile);

/ / para cada registro del conjunto de da tos


Printer.Canvas.Font.Sty1e : = [ I ;
w h i l e not data.EOF do
begin
/ / imprime cada campo del registro
f o r I : = 0 t o data-Fieldcount - 1 do
begin
sizeStr : = IntToStr (min
(data.Fields [i].Displaywidth, maxSize) ) ;
Write (PrintFile, Format ( ' % - I + sizeStr + 's',
[data.Fields [i].Asstring]) ) ;
end;
Writeln (PrintFile);
// avanza la ProgressBar
progress.Position : = progress.Position + 1;
data.Next;
end;
finally
// cierra el conjunto de da tos
data.Close;
end ;
finally
/ / reasigna la fuente de impresion original
01dFont.Free;
end ;
finally
// cierra la impresora/archivo
CloseFile (PrintFile);
end ;
end ;

El programa recurre a esta rutina cuando se hace clic sobre el boton Print All.
El programa ejecuta una consulta independiente (select count ( * ) f r o m
EMPLOYEE), que devuelve el numero de registros de la tabla de empleados. Esta
consulta es necesaria para preparar la barra de progreso (el conjunto de datos
unidireccional, en realidad, no tiene forma alguna de conocer el numero de regis-
tros que va a recuperar hasta que ha alcanzado el ultimo). A continuacion, define
la fuente de la salida, usando posiblemente una fuente con un ancho fijo, y llama
a la rutina PrintOutDataSet:
p r o c e d u r e TNavigator.PrintAllButtonClick(Sender: TObject);
var
Font: TFont;
begin
// d e f i n e e l r a n g o d e l a P r o g r e s s B a r
EmplCountData.Open;
try
ProgressBarl .Max : = EmplCountData. Fields [0] .AsInteger;
finally
Emp1CountData.Close;
end;

Font : = TFont .Create;


try
Font. Name : = ' C o u r i e r New' ;
Font.Size : = 9;
PrintOutDataSet (EmplData, ProgressBarl, Font) ;
finally
Font. Free;
end;
end:

Los paquetes y la cache


El componente Client Da t a Se t lee datos en paquetes que contienen el nu-
mero de registros indicados por la propiedad Packet Re cords. El valor
predefinido de esta propiedad es - 1,que significa que el proveedor extraera todos
10s registros a1 mismo tiempo (esto resulta razonable solo en el caso de un peque-
iio conjunto de datos).
Como alternativa, podemos definir su valor como cero para pedir a1 servidor
solo 10s descriptores del campo y no 10s datos reales o usar cualquier valor posi-
tivo para especificar un numero.
Si conseguimos solo un conjunto parcial de datos, cuando exploramos mas
a116 del final de la cache local, si la propiedad FetchOnDemand esta estableci-
da como True (el valor predefinido), el componente C 1 i e n t Da t a Se t extrae-
ra mas registros de su fuente. Esta misma propiedad controla tambien si 10s campos
BLOB y 10s conjuntos de datos anidados de 10s registros actuales se extraen
automaticamente (dichos valores podrian no ser parte todavia del paquete de da-
tos, segun el valor de la propiedad Options del proveedor del conjunto de
datos) .
Si desactivamos esta propiedad, sera necesario extraer manualmente mas re-
gistros, llamando al metodo GetNext Pac ket, hasta que devuelva cero. (Para
estos otros elementos, llamaremos a FetchBlobs y FetchDetails.)
erve que 2mtes de definir un indice para 10s datos,
)doel conj,unto de datos (yendo a su ultimo registro
u U G l l U G l I U U la ylurlGuad Packt ,+D,,,-A,
L L I C C I V L U ~ WIW
-
-I 1. nn
1, ..,.
UG LIU 3 G l -1,

tendremos un indice extraiio basado en datos parciales.

Manipulacion de actualizaciones
Una de las ideas principales quc esta tras el componente C l i e n t D a t a S e t
es que se utiliza como una cache local para obtener la entrada de un usuario y. a
continuacion, enviar un lote de solicitudes de actualizacion a la base de datos. El
componente posee tanto una lista de 10s cambios que se van a aplicar al servidor
de la base de datos, almacenada en el mismo formato usado por el
C l i e n t D a t a S e t (accesiblc a travcs de la propiedad D e l t a ) , como un com-
pleto registro de actualizaciones que podemos manipular con algunos metodos
(incluyendo la capacidad de deshacer 10s cambios).
- . -

TRUCO:En Delphi, las operaciones Applyupdates y Undo del componente


Client D a t aSe t tarnbien son accesibles a traves de acciones predefinidas.

El estado de 10s registros


El componente nos permite realizar un seguimiento de lo que ocurre en 10s
paquetes de datos. El metodo U p d a t e s t a t u s devuelve uno de 10s siguientes
indicadores para el registro actual:
type TUpdateStatus = (usunmodified, usModified, usInserted,
usDeleted) ;

Para comprobar el estado de cada registro en el conjunto de datos del cliente


facilmente? podemos aiiadir un campo calculado de tip0 cadena a1 con-junto de
datos (lo hemos llamado c l i e n t D a t a S e t 1S t a t u s ) y calcular su valor con
el siguiente controlador del evento O n C a l c F i e l d s :
procedure TForml.ClientDataSetlCalcFields(DataSet: TDataSet);
begin
ClientDataSet1Status.AsString : = GetEnurnName
(TypeInfo(TUpdateStatus),
Integer (ClientDataSet1.UpdateStatus));
end;

Este metodo (basado en la funcion RTTI GetEnumName) convierte el valor


actual de la enumeracion T U p d a t e S t a t u s en una cadena, con el efecto que
muestra la figura 14.10.
I
U W ~ StxwDda 1 _I
-. .- . .... - - .. .
Data I -- - - - --- .

JDEPT-NO(EMP-NO
[FIRST-NAME [LAST-NAME IPHDWE-~~T~~ALWY
usUnmodlied 115 118 Takash Yamamlo 23
usUnmod111ed 125 121 Roberto Ferran 1
100 127 Mihael Yanawski 432
123 134 Jacques Glon 937
623 136 colt Johnson 265
usUnmod~fied 621 138 T.J Green 218
urUnmodilied 672 144 John Montgomery 820
usModrfied 622 145 Mark Guckenhr 931
uslnserled 622 146 John Rohd 932

Figura 14.10. El programa CdsDelta rnuestra el estado de cada registro en un


ClientDataSet.

Acceso a Delta
Mas alla de examinar el estado de cada registro, el mejor mod0 de entender que
cambios han sucedido en un ClientDataSet dado (pero no se han cargado aun a1
scrvidor) consiste en fijarse en el delta, la lista de cambios que esperan ser aplica-
dos a1 servidor. Esta propiedad se define como sigue:
property Delta: Olevariant;

El formato usado por la propiedad D e l t a cs cl mismo que el usado para


transmitir 10s datos dcsde el cliente a1 servidor. Lo que podemos hacer es aiiadir
otro componcntc C l i e n t Dat a S e t a una aplicacion y conectarlo a 10s datos de
la propicdad D e l t a del primer conjunto de datos dcl cliente:
.
i f ClientDataSetl Changecount > 0 then
begin
ClientDataSet2.Data : = ClientDataSetl-Delta;
ClientDataSet2.0pen;

En el ejemplo CdsDelta, hemos aiiadido un modulo de datos con 10s dos com-
ponentes C l i e n t Da t a S e t y una fuente de datos: un SQLDataSet proyectado
sobre la tabla EMPLOYEE de muestra de InterBase. Ambos con.juntos de datos
de cliente tienen el campo calculado adicional de estado (status), con una version
ligeramente mas gentrica que el codigo comentado antes, porque el controlador
de eventos es compartido entre ambos.
- -

TRUCO:Para crear carnpos permanentes para el ClientDataSet conectado


a1 delta (en tiempo de ejecucion), lo hemos conectado temporalmente en
tiempo de diseiio a1 mismo proveedor del ClientDataSet principal. La es-
tructura del delta es la misma que la del conjunto de datos a que se refiere.
Despub de crear 10s carnpos permanentes, hemos elirninado la conexi6n.
El formulario de esta aplicacion tiene un control de paginado con dos fichas,
cada una con un DBGrid, uno para 10s datos reales y otro para el delta. Algo de
codigo oculta o muestra la segunda solapa dependiendo de la esistencia de datos
en el registro de cambios, como lo devuelve el metodo Changecount y actuali-
za el delta cuando se selecciona la solapa correspondiente. La p a r k principal del
codigo utilizada para manipular 10s datos delta es muy similar a1 ultimo fragmen-
to de codigo y se puede estudiar el codigo fuente del e.jemplo en el CD.
La figura 14.11 muestra cl registro de cambios de la aplicacion CdsDelta.
Fijese en que el conjunto de datos delta time dos entradas por cada registro
modificado (10s valores originales y 10s campos modificados) a menos que se trate
dc un nuevo registro o uno eliminado, como indica su estado.

SWW IDEPT-NOIEMP-NO IFIRST-NAME (LAST-NAME IPHONE-EXTISALARY I -


a

-) usllnrnnd~kd 600 2 Robert Nelson 250 105900


- usMwhf~ed 251
- usUnrndhed
uslnsertcd 622 146 Jahn Rdand 932 32Q30
- 622 145 Mak Guckenhec 221 3M(JO

- wModtfd 91 -
- usUmd~hed
usD&ed ? 21 141 Pmre Osba~ne llOOW
- 7 23 134 Jacques Gbn 390W
-usModltnj 937

A
Figura 14.1 1. El ejemplo CdsDelta permite ver las solicitudes de actualizacion temporal
almacenadas en la propiedad Delta del ClientDataSet.
- . - - - - .- -- - ~ .

TRUCO: Tambien podemos filtrar el conjunto de datos delta (o cualquier


otro ClientDataSet) dependiendo de su estado de actualizacion, usando la
propiedad Status Filter.Esto permitiria mostrar registros nuevos, ac-
tualizados y borrados en cuadriculas independientes o en una cuadricula
I
con un filtro seleccionando una opcion en un Tabcontrol.

Actualizar 10s datos


Ahora que comprendemos mejor lo que sucede durante las actualizaciones
locales, podemos probar el funcionamiento de este programa mediante el envio de
la actualizacion local (almacenada en el delta) de vuelta al servidor de la base de
datos. Para aplicar todas las actualizaciones desde un conjunto de datos a1 mismo
tiempo, hay que pasar - 1 a1 metodo ApplyUpdates.
Si el proveedor (o el componente Resolver que contiene) tiene problemas para
aplicar una actualizacion, desencadena el evento OnReconcileError . Esto
puede ocurrir debido a una actualizacion concurrente de dos personas distintas.
Es habitual utilizar un bloqueo optimista en aplicaciones clientelservidor, por lo
que esto deberia contemplarse como una situacion normal.
El evento OnRe c o n c i l e E r r o r permite modificar el parametro A c t i o n
(que se pasa como referencia), que determina el mod0 en que deberia comportarse
el servidor:
procedure TForml.ClientDataSet1ReconcileError(DataSet:
TClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind; var Action:
TReconcileAction) ;

Este metodo tiene tres parametros: el componente de conjunto de datos del


cliente (en caso de que haya mas de un conjunto de datos de cliente en la aplica-
cion actual), la excepcion que ocasiono el error (con el mensaje de error) y el tip0
de operacion que fa110 ( u k M o d i f y , u k I n s e r t o u k D e l e t e ) . El valor de
retorno, que almacenaremos en el parametro A c t i o n , puede ser uno de 10s si-
guientes :
type TReconcileAction = (raSkip, raAbort, raMerge, racorrect,
racancel, raRefresh) ;

El valor raSkip: Indica que el servidor deberia omitir el registro conflicti-


vo, dejandolo en el delta (este es el valor predefinido).
El valor raAbort: Dice a1 servidor que interrumpa toda la operacion de
actualizacion y que ni siquiera intente aplicar 10s cambios que quedan en la
lista en delta.
El valor raMerge: Dice a1 servidor que mezcle 10s datos del cliente con
10s datos del servidor, aplicando solo 10s campos modificados de este cliente
(y manteniendo 10s otros campos modificados por otros clientes).
El valor racorrect: Dice a1 servidor que sustituya sus datos por 10s datos
actuales del cliente, sobrescribiendo todos 10s cambios de campo ya reali-
zados por otros clientes.
El valor racancel: Cancela la solicitud de actualizacion, eliminado la
entrada del delta y recuperando 10s valores extraidos originalmente desde
la base de datos (ignorando asi 10s cambios realizados por otros clientes).
El valor raRefresh: Dice a1 servidor que deseche todas las actualizacio-
nes del delta de cliente y las sustituya por 10s valores que estan actualmen-
te en el servidor (guardando asi 10s cambios realizados por otros clientes).
Para verificar una colision podemos lanzar dos copias de la aplicacion cliente,
modificar el mismo registro en ambos clientes, y enviar despues las actualizacio-
nes de ambos. Haremos esto mas adelante para generar un error, per0 por ahora
vamos a ver como controlar el evento O n R e c o n c i l e E r r o r .
Controlar este evento no es demasiado dificil, per0 solo porque se nos propor-
ciona algo de ayuda. Ya que crear un formulario especifico para controlar el
evento OnReconci l e E r r o r es muy comun, Delphi ya proporciona dicho for-
mulario en el Object Repository (disponible mediante las opciones de menu
File>New>Otherdel IDE de Delphi). Sencillamente hay que ir a la pagina Dialogs
y seleccionar el elemento Reconcile Error Dialog. Esta unidad exporta una
funcion que podemos usar directamente para inicializar y mostrar el cuadro de
dialogo, como hemos hecho en el ejemplo CdsDelta:
p r o c e d u r e TDmCds.cdsEmployeeReconcileError (DataSet:
TCustomClientDataSet;
E: EReconcileError; UpdateKind: TUpdateKind; v a r Action:
TReconcileAction) ;
begin
A c t i o n : = HandleReconcileError(DataSet, UpdateKind, E);
end:

-- - - -- - --

ADVERTENCIA: Como sugiere el c6digo fuente de la unidad Reconcile


Error Dialog, deberiamos usar el dialogo Project Options para elirninar
este formulario de la lista de formularios creados autodticamente (si no lo
hacemos, habra un error ~uandocompilernos el proyecto). Por supuesto,
necesitamos hacer esto sblo si no hemos configurado Delphi para saltarse
la creacih autowtica de formularios.

La funcion HandleRe c o n c i l e E r r o r sencillamente crea el formulario del


cuadro de dialogo y lo muestra, como se ve en el codigo que proporciona Borland:
f u n c t i o n HandleReconcileError(DataSet: TDataSet; UpdateKind:
TUpdateKind;
ReconcileError: EReconcileError): TReconcileAction;
var
UpdateForm: TReconcileErrorForm;
begin
UpdateForm : = TReconcileErrorForm.CreateForm(DataSet,
UpdateKind,
ReconcileError) ;
w i t h UpdateForm d o
try
i f ShowModal = mrOK t h e n
begin
Result : = TReconcileAction(ActionGroup.Items.Objects[
ActionGroup.ItemIndex]);
i f Result = racorrect t h e n
SetFieldValues (DataSet);
end
else
Result : = raAbort;
finally
Free;
end;
end;

La unidad Reconc, que contiene el dialogo Reconcile Error (una ventana


titulada Update Error habria sido mas comprensible para 10s usuarios finales de
10s programas) contiene mas de 350 lineas de codigo, por lo que no podemos
describirla en detalle. Sin embargo, deberia ser facil comprender el codigo fuente
si se estudia con detenimiento. Ademas, puede usarse sin preocuparse por como
hnciona.
El cuadro de dialogo aparecera en caso de error, informando del cambio solici-
tad0 que causo el conflict0 y permitiendo a1 usuario escoger uno de 10s posibles
valores TReconcileAct ion.Podemos ver un ejemplo en la figura 14.12.

-R d *
c Skip
Cbnd
C Coned
C Rdmh
C Mape
. -- -

Fild Name M d f i Vdue Ih f l i c ! i n g ~ a b a 10iipnd ahr re 4


EMP-NO I CUnchanaed, tunchanoed, 2
<Unchanged, <Unchanged> Robert
<Unchanged> <Unchanged> Nelson
tUnchmgecb <Unchanged> 105900
PHONE-W 251 333 250

Figura 14.12. El dialogo Reconcile Error que ofrece Delphi en el Object Repository y
que usa el ejemplo CdsDelta.

TRUCO:Cuando llamamos a App 1yUpda t es, iniciamos una secuencia


de actualization compleja que se comentara mas adelante en el capitulo 16
para las arquitecturas multicapa. En resumen, el delta se envia a1 provee-
dor, que activa el evento OnUpdateData y, a continuacibn, recibe un
evento Bef oreUpdateRecord para todo registro que se va a actuali-
zar. Estas son las dos oportunidades de que disponemos para cornprobar
10s cambios y forzar operaciones especificas en el servidor de base de da-
tos.

Uso de transacciones
Si trabajamos con un servidor SQL, deberiamos usar transacciones para que
nuestras aplicaciones fbese mas robustas. Podemos pensar en una transaccion
como una serie de operaciones que se consideraran como un todo unico, "atomi-
cow,que no se puede dividir.
Un ejemplo puede ayudar a aclarar la idea. Supongamos que tenemos que
aumentar el sueldo de cada empleado de una empresa un tanto por ciento dado,
como en el ejemplo Total del capitulo 13. Un programa tipico ejecutaria una serie
de sentencias SQL en el servidor, una para cada registro que necesite ser actuali-
zado. Si se produjera un error en esa operacion, podriamos deshacer 10s cambios
anteriores. Si se considera la operacion "aumentar el sueldo de cada empleado"
como una unica transaccion, deberia realizarse completamente o ignorarse com-
pletamente. 0, podemos considerar la analogia con las transacciones bancarias:
si un error provoca que solo se realice parte de la operacion, podriamos acabar
con dinero de menos o de mas.
Trabajar con operaciones de bases de datos como transacciones resulta muy
util. Se puede comenzar una transaccion y realizar varias operaciones que debe-
rian considerarse todas ellas como parte de una operacion mayor. Entonces, a1
final, se pueden confirmar 10s cambios o deshacer la transaccion, descartando
todas las operaciones realizadas hasta el momento. Lo mas tipico es que se quiera
deshacer una transaccion si se produce un error en alguna de sus operaciones.
Existe otro punto que merece la pena resaltar: las transacciones sirven tambien
durante la lectura de datos. Hasta que 10s datos Sean confirmados por una tran-
saccion, otras conexiones y/o transacciones no deberian verlos. Una vez que se
hayan confirmado 10s datos procedentes de una transaccion, otras deberian ver el
cambio cuando lean 10s datos, es decir, a menos que se necesite abrir una transac-
cion y volver a leer 10s mismos datos para realizar un analisis de 10s mismos o
complejas operaciones de generacion de informes. Distintos servidores SQL per-
miten leer datos en una transaccion de acuerdo con alguna de o todas estas alter-
nativas, como veremos cuando analicemos 10s niveles de aislamiento de
transacciones.
Es muy sencillo controlar las transacciones en Delphi. Por defecto, cada ope-
ration de edicion y rnodificacion se considera como una transaccion implicita
unica, per0 podemos modificar este comportamiento controlando las operaciones
explicitamente. Simplemente usamos 10s siguientes tres metodos del componente
SQLConnection de dbExpress (otros componentes de conexion de bases de datos
tienen metodos similares):
StartTransaction: Marca el comienzo de la transaccion.
Commit: Confirma todas las actualizaciones de la base de datos realiza-
das durante la transaccion.
Rollback: Devuelve la base de datos a su estado anterior a la transaccion.
Podemos utilizar tambien la propiedad In T r a n s a c t i o n para comprobar si
una transaccion esta activada. Lo mas normal es usar un bloque t r y para desha-
cer una transaccion cuando se lanza una excepcion, o se puede confirmar la tran-
saccion como ultima operacion del bloque t r y , que se ejecutara solo cuando no
se produzcan errores. El codigo podria tener este aspecto:
var
TD: TTransactionDesc;
begin
TD.TransactionID : = 1;
TD.IsolationLeve1 := xilREADCOMMITTED;
SQLConnectionl.StartTransaction(TD);
t rY
/ / - - l a s o p e r a c i o n e s d e la t r a n s a c c i o n v a n a q u i - - -
SQLConnectionl .Commit (TD);
except
SQLConnectionl .Rollback (TD);
end;

Cada metodo relacionado con la transaccion tiene un parametro que describe


la transaccion con la que trabaja. El parametro utilizaa el tip0 de registro
T T r a n s a c t ionDesc y equivale a un nivel de aislamiento de la transaccion y a
un identificador. El nivel de aislamiento de la transaccion es una indicacion de
como deberia comportarse la transaccion cuando otras transacciones modifiquen
10s datos. Los tres valores predefinidos son 10s siguientes:
tiDirtyRead: Hace que la actualizaciones de la transaccion resulten visi-
bles inmediatamente para otras transacciones y usuarios, antes incluso de
que se confirmen. Se trata de la unica posibilidad para algunas bases de
datos y se corresponde con el comportamiento de las bases de datos sin
soporte de transacciones.
tiReadCommitted: Hace que esten disponibles para otras transacciones
so10 las actualizaciones que ya han sido confirmadas. Este valor es el
recomendado para la mayor parte de las bases de datos, para conservar la
eficacia.
tiRepeatableRead: Oculta 10s cambios de cualquier otra transaccion ini-
ciada por otros usuarios despues de la actual, incluso aunque se hayan
confirmado 10s cambios. Llamadas repetidas en una transaccion produci-
ran siempre el mismo resultado, como si la base de datos tomara una ins-
tantanea de 10s datos cuando comienza la transaccion actual. Solo InterBase
y algunos otros servidores de bases de datos funcionan con eficiencia me-
diante este modelo.

I TRUCO:Comd sugcrencia general, por motivos de rendimicnto, lai tran- I


wcciones deberlan irnplicar un numero minimo de actualizaciones (solo
quellas estrictamdte %dadonadasentre si y parte de una linica operacion
Btomica) y deberl'an romar poco tiempo. Deberian evitarse transacciones
&e apcren enfrada del uruario gara completane, ya que el usuario podria
..
rapidas, porque se puede abrir una transaccion para lectura, cerrarla, y des-
puts abrir una transaccion para escribir todo el lote de cambios.

El otro campo del registro TTransac t ionDesc contiene un identificador


dc transaccion. Solo es util junto con un servidor de bascs de datos que soporte
varias transacciones concurrentes sobre la misma conexion, como InterBase. Se
puede preguntar a1 componente de conexion si el servidor soporta varias transac-
ciones o si no soporta las transacciones en absoluto, usando las propiedades
MultipleTransactionsSupported y Transactionssupported.
Cuando el servidor soporta transacciones multiples, hay que proporcionar a
cada transaccion un identificador unico cuando se llame a1 metodo S t a r t T r a n -
saction:
var
TD: TTransactionDesc;
begin
TD-TransactionID : = GetTickCount;
TD.IsolationLeve1 : = xilREADCOMMITTED;
SQLConnectionl.StartTransaction(TD);
SQLDataSet1.TransactionLevel : = TD.TransactionID;

Tambien se puede indicar que conjuntos de datos pertenecen a que transaccion


fijando la propiedad Transact ionLevel de cada conjunto de datos al valor
de un identificador de transaccion. como se muestra en la ultima sentencia.
Para seguir realizando y experimentando con 10s niveles de aislamiento de
transacciones, se puede usar la aplicacion TranSample. Como vemos en la figura
14.13, 10s botones de radio permiten escoger las alternativas y 10s botones de
pulsador permiten trabajar sobre las transacciones y aplicar actualizaciones o
refrescar 10s datos. Para captar la verdadera idea de 10s distintos efectos, deberia-
mos ejecutar varias copias del programa (siemprc que tengamos suficientes licen-
cias en el servidor InterBase)

NOTA: InterBase no soporta el mod0 de lectura sucia (tiDirtyRead),


por lo que en el programa TranSample no se podra utilizar la ultima opci6n
a no ser que se trabaje con un sewidor distinto.

Uso de InterBase Express


Los ejemplos creados en este capitulo se crearon mediante la nueva biblioteca
de base de datos dbExpress. Usando este enfoque independiente de servidores,
podemos cambiar el servidor de bases de datos utilizado por la aplicacion, aunque
en la practica no resulte tan sencillo. Si la aplicacion que vamos a crear empleara
siempre una misma base de datos, se pueden escribir programas enlazados direc-
tamente con la API de ese servidor especifico.
Este enfoque hara que 10s programas Sean explicitamente no adaptables a
otros servidores SQL

Figura 14.13. El formulario de la aplicacion Transample en tiempo de diseRo


Los botones de radio permiten configurar distintos niveles de aislamiento de
transaccion.

Por supuesto, generalmente no utilizaremos directamente estas API, sino que


basaremos el desarrollo en otros componentes de conjuntos de datos como envol-
torio de dichas API y que encajen de forma natural en Delphi y en la arquitectura
de su biblioteca de clases. Un ejemplo de este tip0 de familia de componentes es
InterBase Express (IBX). Las aplicaciones creadas usando estos componentes
deberian funcionar mejor y mas rapido (siquiera ligeramente), ofreciendo un
mayor control sobre las caracteristicas especificas del servidor. Por ejemplo,
IBX proporciona un conjunto de componentes administrativos especificos para
InterBase 6.
-

NOTA: Analizaremos 10s componentes IkX porque e s t h ligados con


InterBase (el servidor de bases de h s tm@do en a t e capitulo) y porque
es el h i c o conjunto de componentes dispolxible en la instala~i6nesthdar
de Delphi. Otros conjuntos sirnilares (para InterBase, Oracle y o m servi-
dores) son igualmente potentes y bien acogidos entre la muaidad de pro-
gramadores de Delphi. Un buen ejemplo Cy una altemtiva a IgX) es
InterBase Objects (www.ibobjeds.corn).
Componentes de conjunto de datos IBX
Los componentes IBX abarcan componentes de conjuntos de datos
personalizados y algunos otros. Los componentes de conjuntos de datos heredan
de la clase basica TDataSet, pueden usar todos 10s controles data-aware habi-
tuales de Delphi y proporcionan un editor de campo y todas las funciones en
tiempo de diseiio habituales. Se pueden escoger varios componentes de conjuntos
de datos. Tres conjuntos de datos de IBX tienen una funcion y un conjunto de
propiedades parecidos a 10s componentes de tabla, peticion y procedimiento al-
macenado de la familia dbExpress:
1BTable: Se parece a1 componente Table y permite acceder a una tabla-o
vista unica.
IBQuery: Se parece a1 componente Query y permite ejecutar una consulta
SQL que devuelve un conjunto resultado. El componente IBQuery puede
usarse junto con el componente IBUpdateSQL para obtener un conjunto de
datos en vivo (o que pueda editarse).
IBStoredProc: Se parece a1 componente StoredProc y permite ejecutar un
procedimiento almacenado.
Estos componentes, como todos 10s relacionados con dbExpress, estan pensa-
dos para ser compatibles con 10s antiguos componentes BDE que podrian usarse
en las aplicaciones. Para las aplicaciones nuevas, deberiamos usar en general el
componente IBDataSet, que permite trabajar con un conjunto de resultados en
vivo obtenido a1 ejecutar una consulta s e l e c t . Basicamente combina IBQuery
con IBUpdateSQL en un componente unico. De hecho, 10s tres componentes ante-
riores se proporcionan principalmente para mantener la compatibilidad con apli-
caciones Delphi BDE. Muchos otros componentes de InterBase Express no
pertenecen a la categoria de conjuntos de datos, per0 aun asi se usan en aplicacio-
nes que necesitan acceder a una base de datos:
IBDatabase: Funciona como el componente SQLConnection de DBX y se
usa para establecer la conexion con la base de datos. El BDE utiliza tam-
bien el componente especifico Session para realizar algunas tareas globales
realizadas por el componente IBDatabase.
IBTransaction: Proporciona un control total sobre las transacciones. Es
importante utilizar transacciones explicitamente en InterBase y aislar cada
transaccion de forma correcta, usando el nivel de aislamiento Snapshot
para 10s informes y el nivel Read Committed para 10s formularios
interactivos. Cada conjunto de datos se refiere explicitamente a una tran-
saccion determinada, por lo que podemos tener varias transacciones con-
currentes frente a la misma base de datos, escogiendo que conjuntos de
datos participan en que transaccion.
IBSQL: Nos permite ejecutar sentencias SQL que no devuelven un con-
junto de datos (por ejemplo, solicitudes DDL o sentencias u p d a t e y
d e l e t e ) sin la sobrecarga de un componente de conjunto de datos.
IBDatabaseInfo: Se usa para consultar la estructura y estado de la base
de datos.
IBSQLMonitor: Se utiliza para depurar el sistema, puesto que el depura-
dor SQL Monitor que ofrece Delphi es una herramienta especifica de BDE.
IBEvents: Recibe eventos enviados por el servidor.
Este grupo de componentes ofrece mayor control sobre el servidor de bases de
datos del que podemos conseguir con dbExpress. Por ejemplo, tener un compo-
nente especifico de transaccion permite controlar varias transacciones concurren-
tes en una o varias bases de datos, asi como una transaccion unica que se realice
sobre varias bases de datos. El componente IBDatabase permite crear bases de
datos, comprobar la conexion y, normalmente, acceder a datos del sistema, algo
que 10s componentes Database y Session de BDE no permiten del todo.

TUCO: Los conhnt& he datos


rniento automatic& de
un
ie%ten co&Grar el d o m p o r
generador c o i o una especie de campo de incre-
rnento automati'ca. Para ello, se establece la propiedad G e n e r a t o r F i e l d
utilizando su edit# de propiedad especifico.
.i.

Componentes administrativos IBX


Una nueva ficha de la Component Palette de Delphi, InterBase Admin,
contiene componentes administrativos de InterBase. Aunque nuestro objetivo no
sea probablemente crear una completa aplicacion de consola de InterBase, puede
que sea razonable incluir algunas prestaciones administrativas (como el manejo
de copias de seguridad o el seguimiento de 10s movimientos del usuario) en apli-
caciones destinadas a usuarios avanzados.
La mayoria de estos componentes poseen nombres autoexplicativos:
IBConfigService, IBBackupService, IBRestoreService, IBValidationService,
IBStatisticalService, IBLogService, IBSecurityService, IBServerProperties,
IBInstall e IBUninstall. No vamos a crear ningun ejemplo avanzado que utilice
estos componentes, ya que estan mas orientados a1 desarrollo de aplicaciones de
administracion del servidor que de programas de cliente. Sin embargo, incluire-
mos algunos de ellos en el ejemplo IbxMon que veremos mas adelante.

Creacion de un ejemplo IBX


Para crear un ejemplo que utilice IBX, necesitaremos colocar en un formulario
(o modulo de datos) a1 menos estos tres componentes: un IBDatabase, un
IBTransaction y un componente de conjunto de datos (en este caso un IBQuery).
Cualquier aplicacion IBX necesita a1 menos una instancia de 10s dos primeros
componentes. No se pueden establecer conexiones a bases de datos en un conjunto
de datos de IBX, como si se podia con otros conjuntos de datos.
Y, es necesario a1 menos un objeto de transaccion para leer siquiera el resulta-
do de una consulta. Estas son las propiedades claves de estos componentes en el
ejemplo IbsEmp:
o b j e c t IBTransactionl: T I B T r a n s a c t i o n
A c t i v e = False
DefaultDatabase = IBDatabasel
end
o b j e c t IBQueryl: T I B Q u e r y
Database = IBDatabasel
T r a n s a c t i o n = IBTransactionl
CachedUpdates = False
SQL-Strings = (
'SELECT * FROM E M P L O Y E E ' )
end
o b j e c t IBDatabasel: TIBDatabase
DatabaseName = ' C : \Archives d e p r o g r a m \ InterBase ' +
'Corp\InterBase6\examples\Databd~e\employee.gdb'
Params .Strings = (
' user-name=SYSDBA1
' p a s s w o r d = m s t e r k e y ')
Loginprompt = False
IdleTimer = 0
SQLDialect = 1
TraceFlags = [ I
end

Ahora podemos conectar un componente Datasource a IBQueryl y crear


facilmente una interfaz de usuario para la aplicacion. Hemos escrito el nombre de
ruta de la base de datos de muestra de Borland.
Sin embargo, no todo el mundo time la carpeta Archivos de programa,
que depende de la version local de Windows y 10s archivos de datos de muestra de
Borland podrian estar en cualquier otra parte del disco. Resolveremos estos pro-
blemas en el proximo ejemplo.

ADVERTENCIA: Fijese en que hemos incluido la contrasefia en el d i -


go, una ttcaica de'seguW bastante inocente. No r& @& ejecutar el
p r o p m a euaIqnier persma, sho que d m S s alguiea podria extraer hdu-
so ?accantraseiia fijhdase e n d c w o hexadecimal dd rlrchivo ejeoutable.
us& esta tecnica para que no fuese necegario &crib@una y otra
vez q e s * contrasefia p1:pro'Liar 81p t ~ g mper6
, en una a @ l i c a d bred
deberiarnoi!pedir a h usuanos @e lo hickran asi, si Wremos gariintixar
la segutidad de nuestros datos.
Creacion de una consulta en vivo
El ejemplo l bxEmp incluye una consulta que no permite la edicion. Para ac-
tivarla, es necesario utilizar un componente IBUpdateSQL a la consulta, aun-
que se trate de una consulta muy sencilla. Utilizando un componente I BQuery
que albergue la sentencia SQL se 1e ct,junto con un componente IBUpdateSQL
que contenga las sentencias SQL insert,update y delete es un enfoque
tipico de las aplicaciones BDE. El parecido entre estos componentes hace mas
facil adaptar una aplicacion BDE ya existente a esta arquitectura. Este es el
codigo para estos componentes (editado por cuestion de claridad):
object IBQueryl: TIBQuery
Database = IBDatabasel
Transaction = IBTransactionl
SQL.Strings = (
'SELECT Employee-EMP-NO, Department.DEPARTMENT,
Employee.FIRST-NAME, ' +
I Employee.LAST-NAME, Job.JOB-TITLE, Employee-SALARY,
Employee.DEPT-NO, ' +
' Employee.JOB-CODE, Employee.JOB-GRADE,
Employee.JOB-COUNTRY'
'FROM EMPLOYEE Employee'
' INNER JOIN DEPARTMENT Department'
I ON (Department.DEPT-NO = Employee.DEPT-NO) '
' INNER JOIN JOB Job'
ON (Job.JOB-CODE = Employee. JOB-CODE)
' AND ( Job.JOB-GRADE = Employee . JOB-GRADE) '
' AND (Job.JOB-COUNTRY = Employee.JOB-COUNTRY) '
'ORDER BY Department.DEPARTMENT, Employee.LAST-NAME')
Updateobject = IBUpdateSQLl
end
object IBUpdateSQLl: TIBUpdateSQL
RefreshSQL.Strings = (
'SELECT Employee-EMP-NO, Employee.FIRST-NAME,
Employee.LAST-NAME, ' +
'Department.DEPARTMENT, Job-JOB-TITLE,
Employee.SALARY, Employee.DEPT-NO,'+
'Employee-JOB-CODE, Employee.JOB-GRADE,
Employee-JOB-COUNTRY'
' FROM EMPLOYEE Employee '
'INNER JOIN DEPARTMENT Department'
'ON (Department-DEPT-NO = Employee-DEPT-NO) '
'INNER JOIN JOB Job'
'ON (Job.JOB CODE = Employee. JOB-CODE) '
'AND (Job.JOB-GRADE = Employee. JOB-GRADE) '
'AND (Job.JOB-COUNTRY = Employee.JOB-COUNTRY) '
'WHERE Employee.EMP-NO=:EMP-NO')
ModifySQL. Strings = (
'update EMPLOYEE'
'set'
I FIRST-NAME = : FIRST-NAME, '
' LAST-NAME = :LAST-NAME , '
' SALARY = :SALARY, '
' DEPT-NO = :DEPT-NO, '
' JOB-CODE = :JOB-CODE,'
' JOB-GRADE = : JOB-GRADE, '
' JOB-COUNTRY = :JOB-COUNTRY'
'where'
' EMP-NO = :OLD-EMP-NO ' )
InsertSQL-Strings = (
'insert into EMPLOYEE'
' ( FIRST-NAME, LAST-NAME, SALARY, DEPT-NO, JOB-CODE,
JOB-GRADE, JOB-COUNTRY) '
' values '
' (:FIRST-NAME,:LAST-NAME,:SALARY,:DEPT-NO,:JOB-CODE,:JOB-GRADE,
: JOB-COUNTRY) ' )
DeleteSQL. Strings = (
' delete from EMPLOYEE '
' where EMP-NO = :OLD-EMP-NO ' )
end

Para aplicaciones nuevas, deberia considerarse usar el componente


I BDataSet,que agrupa las prestaciones de IBQuery e IBUpdateSQL. Las di-
ferencias entre usar 10s dos componentes o el componente unico son minimas.
Usar IBQuery e IBUpdateSQL es un enfoque mejor cuando se adapta una aplica-
cion ya existente basada en 10s dos componentes BDE equivalentes, aunque si se
adaptara el programa directamente a1 componente I BDataSet,no seria necesa-
rio un esfuerzo adicional muy grande.
En el ejemplo IbxUpdSql, hemos proporcionado ambas alternativas para que
puedan probarse directamente las posibles diferencias. Este es el esqueleto de la
definicion DFM del componente de conjunto de datos:
o b j e c t IBDataSetl: TIBDataSet
Database = IBDatabasel
Transaction = IBTransactionl
DeleteSQL-Strings = (
' d e l e t e f r o m EMPLOYEE'
' w h e r e EMP-NO = :OLD-EMP-NO')
InsertSQL-Strings = (
' i n s e r t i n t o EMPLOYEE'
' (FIRST-NAME, LAST-NAME, SALARY, DEPT-NO, JOB-CODE,
JOB-GRADE, ' +
' JOB-COUNTRY) '
'values'
' (:FIRST-NAME, :LAST-NAME, :SALARY, :DEPT-NO,
:JOB-CODE, ' +
' :JOB-GRADE, :JOB-COUNTRY) ' )
SelectSQL.Strings = ( . . ).
UpdateRecordTypes = [cusunmodified, cusModified,
cusInserted]
ModifySQL.Strings = ( . . . )
end
Si se conecta el componente I B Q u e r y l o el componente I B D a t a S e t 1a la
fuente de datos y se ejecuta el programa, se vera que su comportamiento es iddn-
tico. No solo tienen 10s componentes un efecto similar; incluso las propiedades y
eventos disponibles son similares.
En el programa IbxUpdSql hemos hecho que la referencia a la base de datos
sea un poco mas flexible. En lugar de escribir el nombre de la base de datos en
tiempo de diseiio, hemos estraido el directorio de datos compartidos de Borland
desde el Registro de Windows (en el que Borland lo guarda durante la instalacion
de Delphi). Este es el codigo que se ejecuta cuando se inicia el programa:
uses
Registry;

p r o c e d u r e TForml.FormCreate(Sender: TObject);
var
Reg: TRegistry;
begin
Reg : = TRegistry.Create;
try
Reg.RootKey : = HKEY-LOCAL-MACHINE;
Reg.OpenKey('\Software\Borldnd\Borland
S h a r e d \ C u r r e n t V e r s i o n l , False) ;
1BDatabasel.DatabaseName : =
Reg. Readstring ( ' R o ot D i r e c t o r y l) +
'exarnples\da t a b a s e \ e m p l o y e e . g d b l;
finally
Reg. Free;
end ;
EmpDS.DataSet.Open;
end;

Otra caracteristica de este ejemplo es la presencia de un componente de tran-


saccion. Como ya hemos dicho, 10s componentes de InterBase Express utilizan un
componente de transaccion obligatorio, siguiendo de forma explicita un requisito
de InterBase. Bastaria con sencillamente aiiadir un par de botones a1 formulario
para confirmar o deshacer la transaccion, porque se inicia una transaccion de
forma automatica cuando editamos cualquier conjunto de datos conectados a la
misma. Tambien hemos mejorado el programa ligeramente aiiadiendole un com-
ponente ActionList. Este incluye todas las acciones estandar de bases de datos y
aiiade dos acciones personalizadas para soporte de transacciones: commit y
R o l l b a c k . Ambas acciones se habilitan cuando la transaccion esta activa:
procedure TForml.ActionUpdateT~ansactions(Sender: TObject);
begin
acCommit.Enabled : = 1BTransactionl.InTransaction;
acRollback.Enabled : = acCommit.Enabled;
end;

Cuando se ejecutan, realizan la operacion principal pero tambien necesitan


abrir de nuevo el conjunto de datos en una nueva transaccion (lo que tambien
puede hacerse mediante "retencion" del context0 de transaccion). En realidad,
Cornrni t R e t a i n i n g no reabre una nueva transaccion, sino que permite que la
transaccion actual permanezca abierta. Asi, podemos seguir usando 10s conjuntos
de datos, que no se refrescaran (por lo que no veremos las ediciones ya confirma-
das por otros usuarios) per0 seguiran mostrando 10s datos que hemos modificado.
~ s t es
e el codigo:
procedure TForml.acCommitExecute(Sender: TObject);
begin
1BTransactionl.CommitRetaining;
end;

procedure TForml.acRollbackExecute(Sender: TObject);


begin
1BTransactionl.Rollback;
/ / r e a b r e e l c o n j u n t o de d a t o s e n una n u e v a t r a n s a c c i o n
1BTransactionl.StartTransaction;
EmpDS.DataSet.Open;
end;

ADVERTENCIA: Debemd Wer en euenta que LntkrBase cierra cud-


quier cursor ihierto cnanilkifid h a una trans&i6n, lo coal significa que
tenemos
. - - .' )y-v.oher a adquirir
que reabrirlo . .aunque no hayarnos
- 10s datos, -. -
hecho cas~blos.Bn cambib, cumdo continnirmos 10s datos podemos pedtrle
a Interwe que. Verigi el "amtptto de transa&ibp1?(que no rcierre 10s
conjut$& ck, abiartoij;,&do uqa b r h ~ogmit~etaining, cmo
ya meficioiamott. L& d ' d eeste winpottamiedto i28 InteiSw 6s que una
trmsaccih se coneqxmde con una instantheor i k lox dams. thando la
tfansa&Bn iia termhtatlo.. se ~~e que 18- b r d m i & ndetro para
voher a ewaer 10s regigtros que pdedad habm si& mcrdificadcm*porotros
US~&&. La v e ~ i S n60
. tie InftrBase lndh$e tambftn bnzt orden
~ o $ . & b akc~ e t ining,
a que Bemqs.$eo;dndp nvusar, porque ep ma ope-
ra&n de retorno o rolback, &program& deberia refrescar 10s &tog del
conjunto de datos para mosq* loa valc&~ originales eq.pantalh n~ las
actualizaciones que hemos descartado,.

La ultima operacion se refiere a un conjunto de datos generic0 y no a uno


especifico porque vamos a aiiadir un segundo conjunto de datos alternativo a1
programa. Las acciones estan conectadas a una barra de herramientas de solo
texto, como muestra la figura 14.14. El programa abre el conjunto de datos a1
arrancar y cierra automaticamente la transaccion actual a1 terminar, tras haber
preguntado a1 usuario que hacer, con el siguiente controlador de eventos Onclose:
procedure TForml.FormClose(Sender: TObject; var Action:
TCloseAction) ;
var
nCode: Word;
begin
if 1BTransactionl.InTransaction then
begin
nCode : = MessageDlg ( ' Commit T r a n s a c t i o n ? (No t o
rollback) ' ,
mtconfirmation, mbYesNoCance1, 0 ) ;
case nCode of
mrYes: 1BTransactionl.Commit;
mrNo: 1BTransactionl.Rollback;
mrcancel: Action : = caNone; // no cierra
end ;
end;
end:

212850 0

Customer S ~ v c e o Enpneer 35000 6


Cuslomar Sewces Manager 56295 6
De Sowa Customer Support Engnear 69482 63 6
Cwtmsr Support Enpneec 56034 38 6
Cwlmer S ~ p o l t Engmeer 35630 6-
15 Kalherme Customr S u p 1 1 Manaper 67241 29 6
Customer Sqlpolt T e c t w dW r m 6W00 6
Engneermg Vm P~eodanl 1E900 6
Engneumg Admrr&abve Assrdanl 27000 6
Ewcpean Headquafiers Sdes h r d n a l a 33620 63 1
Euopean HsadqvaRers
EwopeanHeadquarters
Admrustrake Asrdanl
Eng~lee~
2335 1
3922406 1 -
2L
Figura 14.14. La salida del ejemplo IbxUpdSql.

Control en InterBase Express


A1 igual que la arquitectura dbExpress, IBX tambikn permite controlar el esta-
do de una conexion. Se puede incluir una copia del componente IBSQLMonitor
en una aplicacion y crear un registro personalizado.
Incluso podemos escribir una aplicacion de monitorizacion mas general, como
hemos hecho en el ejemplo IbxMon. Hemos colocado en su formulario un compo-
nente de seguimiento y un control RichEdit, y escrito el siguiente controlador
para el evento OnSQL:
procedure TForrnl.IBSQLMonitorlSQL(EventText: String);
begin
if Assigned (RichEditl) then
RichEditl.Lines.Add (TirneToStr (Now) + ' : ' + EventText);
end;

La comprobacion i f A s s i g n e d puede ser util cuando recibimos un mensaje


durante el cierre de la conexion, y se necesita cuando aiiadimos este codigo direc-
tamente a la aplicacion sobre la que vamos a realizar el seguimiento.
Para recibir mensajes desde otras aplicaciones (o desde la actual), tenemos que
activar las opciones de seguimiento del componente IBDatabase. En el anterior
ejemplo IbxUpdSql, las hemos activado todas:
o b j e c t IBDatabasel: TIBDatabase

Si ejecutamos 10s dos ejemplos a1 mismo tiempo, la salida del programa IbxMon
mostrara en una lista 10s detalles de la interaccion del programa IbxUpdSql con
InterBase, como muestra la figura 14.15.

_~ai:l
Slat~ibcsI S a w Froperk: I Utao 1
b 4753PM
[Appbcabon Ibxupdsql]
IBDalabasel [Connect]
6 47 53 PM
[Applrca~onIbxupdsql]
IBTransacllonl [Stad lransact~on]
6 47 53 PM
[Appi catton Ibxupdsql]
IBDataSell Prepae] SELECT Employee EMP-NO, Ewloyee FIRST-NAME. EmployeeLAST-NAME,
Department DEPARTMENT Job JOB-T ITLE, Employee SALARY Employee DEPT-NO Employee JOB-CODE
EmployeeJ 00-GRADE, Employee JOB-COUNTRY
FROM EMPLOYEE Employee
INNER JOlN DEPARTMENT Department
ON (Oepaltment DEPT-NO = EmployeeDEPT-NO)
INNER JOlN JOB Job
-
ON @&JOB-CODE EmployeeJOB-CODE]
AND [JobJOB-GRADE = EmployeeJOB-GRADE]
AND [JobJOB-COUNTRY = Employee JOB-COUNTRY]
ORDER BY Depa~tmentDEPARTMENT

Pbm PLAN SORT [JOIN [EMPLOYEE NATURALJOB INDEX [RDBSFRlMARY2),DEPARTMENT INDEX [RDB
PRIMARYS)]]
6 47 53 PM
[Appl~catlon Ibmpdsqfl
IBDataSetl [Prepare] delete lrom EMPLOYEE
whera
.
EMP-NO OLD-EMF-NO
11
Figura 14.15. La sahda del ejemplo IbxMon, basada en el componente lBMa

Obtencion de mas datos de sistema


El ejemplo IbxMon no solo realiza el seguimiento de la conexion InterBase,
sino que tambien permite consultar algunos parametros del servidor, mediante las
diversas solapas de su control de pagina. El ejemplo incluye algunos componentes
IBX administrativos, que muestran estadisticas del servidor, algunas propiedades
del servidor y todos 10s usuarios conectados. Podemos ver un ejemplo de las
propiedades del servidor en la figura 14.16 y el codigo para extraer 10s usuarios
en el siguiente fragment0 de codigo.

Figura 14.16. La inforrnacion sobre el servidor rnostrada por la aplicacion


IbxMon.

/ / o b t i e n e 10s d a t o s d e l u s u a r i o
1BSecurityServicel.DisplayUsers;
// muestra el nombre d e cada u s u a r i o
f o r i : = 0 t o IBSecurityServicel.UserInfoCount - 1 do
w i t h IBSecurityServicel.UserInfoli] do
RichEdit4 .Lines .Add (Format ( ' U s e r : % s , F u l l N a m e : % s ,
Id: %dl,
[UserName, FirstName + ' ' + LastName, UserId] ) ) ;

Bloques del mundo real


Hasta ahora hemos analizado tecnicas especificas relacionadas con la progra-
macion de InterBase, per0 no hemos profundizado en el desarrollo de una aplica-
cion y 10s problemas que presenta en la practica. En 10s proximos apartados nos
centraremos en algunos problemas tecnicos.
Nando Dessena (que es un gran experto en InterBase) nos ha ayudado a usar
todas estas tecnicas en un seminario sobre la adaptacion de una aplicacion interna
Paradox para InterBase. La aplicacion en cuestion era en ese caso mucho mas
amplia y compleja, y la hemos reducido a solo unas cuantas tablas para que
encajase en el espacio del que disponemos.
TRUCO: La base de datos que comentaremos en este apartado se llama
.
mastering gdb y puede encontrarse dentro de la subcarpeta data del
nArl;nn
V V U ~
mar-
~
p V
net-
a a a UJCU
.ran;t..ln CP
u a p ~ b u n u .U C I Y
mmmnrl~o m - l ; v a r
&aU
i fU
uaU
l C
l + I(
m ~ r l ; n n tT~n t ~ r R o n mP
I ~ L U U I ~ ~ C CUCCIIYQJU
I
nncnl~
UVUPV~CI,

preferiblemente desputs de hacer una copia en una unidad con pe~misosde


escritura para que se pueda interactuar con ella con plena libertad.

Generadores e identificadores
Como hemos dicho antes, somos partidarios del uso de identificadores para
identificar 10s registros de cada tabla de una base de datos.
- -- - --

NOTA: Normalrnente usamos una sola secuencia de identificadores para


todo un sistema, algo que a veces se llama identificador de objeto (OID).
Sin embargo, en este caso, 10s identificadores de las dos tablas deberin ser
imicos. Ya que no podemos saber de antemano quk objetos podrian utilizar-
se en lugar de otros, acloptar un OID global permite una mayor libertad. El
inconveniente es que, si tenemos muchos datos, usar un entero de 32 bits
como identificador podria no resultar suficiente. Por eso, InterBase 6 so-
porta generadores de 64 bits.

Para generar esos identificadores cuando hay varios clientes en funcionamien-


to, mantener una tabla con el valor mas reciente creara problemas, puesto que las
diversas transacciones concurrentes (de diferentes usuarios) veran 10s mismos
valores. Si no usamos tablas, podemos usar un mecanismo independiente de la
base de datos, como 10s bastante amplios GUID de Windows o la, asi denomina-
da, tecnica de alto-bajo (la asignacion de un numero base a cada cliente durante el
arranque [el numero alto] que se combina con un numero consecutivo [el numero
bajo] determinado por el cliente).
Otra tecnica, ligada a las bases de datos, consiste en usar mecanismos internos
para secuencias, indicados con distintos nombres en cada servidor SQL. En
InterBase se llaman generadores. Estas secuencias funcionan y se incrementan de
manera externa a las transacciones, de tal mod0 que proporcionan numeros uni-
cos incluso a usuarios concurrentes (recordemos que InterBase nos obliga a abrir
una transaccion incluso para leer datos).
Ya hemos visto como crear un generador. Esta es la definicion de uno en
nuestra base de datos de muestra, seguida por la definicion de la vista que pode-
mos usar para consultar un nuevo valor:
create generator g-master ;

create view v-next-id (


next-id
) as
select gen-id (g-master, 1) f r o m rdbSdatabase

Dentro de la aplicacion RWBlocks, hemos aiiadido un componente IBQuery a


un modulo de datos (puesto que no necesitamos un conjunto de datos que pueda
editarse) con la siguiente sentencia SQL:
select next-id f r o m v-next-id;

La ventaja, en comparacion con el uso de la sentencia directa, es que es mas


sencillo de escribir y mantener, incluso si el generador subyacente cambia (o si
pasamos a usar una tecnica diferente de forma interna). Ademas, en el mismo
modulo de datos hemos aiiadido una funcion que devuelve un nuevo valor para el
generador:
f u n c t i o n T D m M a i n - G e t N e w I d : Integer;
begin
// d e v u e l v e e l p r o x i m o v a l o r d e l g e n e r a d o r
QueryId.Open;
try
Result : = QueryId.Fields[O].AsInteger;
finally
QueryId.Close;
end;
end;

Este metodo puede llamarse en el evento A f ter Insert de cualquier conjun-


to de datos, para rellenar el valor del identificador:
mydataset.FieldByName ('ID').AsInteger := data-GetNewId;

Como he mencionado, 10s conjuntos de datos IBX se pueden enlazar directa-


mente a un generador, simplificando asi el esquema global. Gracias a1 editor de
propiedad especifico (que muestra la figura 14.17), conectar un campo del con-
junto de datos a1 generador resulta trivial.

.-
BpplyEvenl - -- I

On New Recud I
r OnPost I

Figura 14.17. El editor de la propiedad GeneratorField de 10s conjuntos


de datos IBX.
Fijese en que ambos enfoques son mucho mejores que el enfoque basado en un
disparador de servidor, comentado con anterioridad. En ese caso, la aplicacion
Delphi no conocia el identificador del registro enviado a la base de datos y no
podria refrescarlo. A1 no tener el identificador del registro (que es ademas el
unico campo clave) en Delphi, significa que resulta casi imposible insertar direc-
tamente un valor de este tip0 en una DBGrid. Si se intenta, se vera que se pierde
el valor que se inserta, y solo vuelve a aparecer en caso de realizar un refresco
total.
Usar tecnicas de cliente basadas en codigo manual o en la propiedad
G e n e r a t o r F i e 1d no provoca problemas. La aplicacion Delphi conoce el
identificador (la clave del registro) antes de enviarlo, por eso puede colocarlo
facilmente en una cuadricula y refrescarlo correctamente.

Busquedas sin distincion entre mayusculas


y minusculas
Un aspect0 interesante de 10s servidores SQL en general, no especificamente
de InterBase, tiene que ver con las busquedas sin distinciones entre mayusculas y
minusculas. Supongamos que no queremos mostrar una gran cantidad de datos en
una cuadricula (que es una mala idea en una aplicacion clientelservidor). En lugar
de eso, decidimos permitir que el usuario escriba la parte inicial de un nombre y
despues se filtre una consulta en base a dicha entrada, mostrando solo el conjunto
de registros resultante (mas pequeiio) en la cuadricula. Asi lo hemos hecho para
una tabla de empresas.
Esta busqueda por nombre de empresa se va a ejecutar con bastante frecuencia
y probablemente se realizara sobre una tabla grande. Sin embargo, si buscamos
usando 10s operadores s t a r t i n g w i t h o 1i k e , la busqueda distinguira entre
mayusculas y minusculas, como en la siguiente sentencia SQL:
select * from companies
where name starting with ' win' ;

Para que la busqueda no distinga entre mayusculas y minusculas, podemos


usar la funcion u p p e r en ambas partes de la comparacion para comparar 10s
valores de cada cadena en mayusculas, per0 una consulta similar seria muy lenta,
puesto que no se basara en un indice. Por otra parte, guardar 10s nombres de las
empresas (o cualquier otro nombre) en letras mayusculas no tendria mucho senti-
do, porque cuando tengamos que mostrar 10s nombres, el resultado sera poco
natural (aunque sea muy frecuente en sistemas de informacion antiguos).
Si podemos conseguir algo de espacio de disco y memoria para obtener mayor
velocidad, podemos usar un truco: aiiadir un campo adicional a la tabla para
almacenar el valor en mayusculas del nombre de la empresa y usar un disparador
del servidor para generarlo y actualizarlo. Entonces podemos pedir a la base de
datos que mantenga un indice para la version en mayusculas del nombre, para
acelerar aun mas la operacion de busqueda. En la practica, la definition de tabla
tendra este aspecto:
c r e a t e domain d-uid as integer;
create table companies
(
id d-uid not n u l l ,
name varchar ( 5 0 ) ,
tax-code varchar(l6),
name-upper varchar ( 5 0 ) ,
constraint companies-pk primary key (id)
) ;

Para copiar el nombre en mayusculas de cada empresa en el campo relaciona-


do, no podemos confiar en el codigo de cliente, ya que una inconsistencia de 10s
datos causaria problemas.
En un caso como este, es mejor usar un disparador en el servidor, de tal mod0
que cada vez que cambie el nombre de la empresa, su version en mayusculas se
actualice de manera apropiada. Para insertar una nueva empresa, se utiliza otro
disparador:
create t r i g g e r companies-bi f o r companies
a c t i v e before i n s e r t p o s i t i o n 0
as
begin
new. name-upper = upper (new.name) ;
end;

c r e a t e t r i g g e r companies-bu f o r companies
a c t i v e before update p o s i t i o n 0
as
begin
i f (new.name <> old.name) then
new. name-uppe r = upper (new.name) ;
end;

Por ultimo, hemos aiiadido un indice a la tabla con esta sentencia DDL:
create index i-companies-name-upper on companies(name-upper) ;

Con esta estructura interna, podemos seleccionar todas las empresas que
comiencen con el texto del cuadro de edicion (edsearch) escribiendo el siguiente
codigo en una aplicacion Delphi:
dm.DataCompanies.Close;
dm.DataCompanies.Se1ectSQL.Text :=
' s e l e c t c . i d , c.name, c. tax-code,' +
' from companies c ' +
' w h e r e name-upper s t a r t i n g w i t h "' +
Uppercase (edsearch.Text) + ' ' ' ' ;
dm.DataCompanies.0pen;
1 TRUCO: Usando una consulta preparada con p a r b e t r o s , podriamos ha- I
I cer que el codigo fuese a h msJ rapido.
I
Como alternativa, se podria crear un campo calculado de servidor en la defini-
cion de tabla, per0 hacer esto impediria tener un indice en el campo, que acelera
las consultas en gran medida:
name-upper varchar (50) computed by (upper (name))

Manejo de ubicaciones y personas


Tal vez se haya observado que la tabla que describe las empresas esta muy
vacia. De hecho, no tiene direcciones de empresas, ni informacion de contacto. La
razon es sencilla: queremos poder manejar empresas que tengan varias oficinas (o
ubicaciones) y proporcionar informacion de contacto sobre varios empleados de
dichas empresas.
Cada ubicacion esta enlazada a una empresa. Observe, sin embargo, que he-
mos decidido no usar un identificador de ubicacion relacionado con la empresa
(como un numero progresivo de ubicacion para cada empresa), sino un identificador
global para todas las ubicaciones. Asi, podemos hacer referencia a un identificador
de ubicacion (para enviar productos, por ejemplo) sin tener que hacer referencia
tambien al identificador de la empresa. Esta es la definicion de la tabla que alber-
ga las ubicaciones de las empresas:
create table locations
(
id d-uid not null,
id-company d-uid not null,
address varchar ( 4 0 ) ,
town varchar ( 3 0 ) ,
zip varchar ( 10 ) ,
state varchar (4) ,
phone varchar ( 15 ) ,
fax varchar ( 15 ) ,
constraint locations-pk primary key (id),
constraint locations-uc unique (id-company, id)
) ;

alter table locations add constraint locations-fk-companies


foreign key (id-company) references companies (id)
on update no action on delete no action;

La definicion final de una clave externa relaciona el campo i d company de


la tabla de ubicaciones con el campo identificador de la tabla deempresas. La
otra tabla lista 10s nombres e informacion de contacto de personas en ubicaciones
especificas de las empresas. Para seguir las reglas de normalizacion de bases de
datos, deberiamos aiiadir a esta tabla solo una referencia a la ubicacion, puesto
que cada ubicacion esta relacionada con una empresa. Sin embargo, para que
resulte mas sencillo modificar la ubicacion de una persona en una empresa y para
que las consultas sean mas eficientes (evitando un paso adicional), hemos aiiadido
a la tabla sobre personas una referencia a la ubicacion y una referencia a la
empresa. La tabla tambien tienen otra caracteristica no habitual: una de las perso-
nas que trabaja para una empresa se puede definir como el contacto clave. Para
ello se usa un campo booleano (definido con un dominio, dado que InterBase no
soporta el tip0 booleano) y aiiadiendo disparadores a la tabla para que solo un
empleado de cada empresa tenga activo dicho indicador:
c r e a t e domain d-boolean a s c h a r (1)
default ' P'
check (value i n ( ' T' , ' P ' ) ) n o t n u l l

c r e a t e t a b l e people
(
id d-uid n o t n u l l ,
id-company d-uid n o t n u l l ,
id-location d-uid n o t n u l l ,
name v a r c h a r (50) n o t n u l l ,
phone v a r c h a r ( 15 ) ,
fax v a r c h a r ( 15 ) ,
email v a r c h a r (50) ,
key-contact d-boolean,
c o n s t r a i n t people-pk primary key (id),
c o n s t r a i n t people-uc unique (id-company, name)
) ;

a l t e r t a b l e people add c o n s t r a i n t people-fk-companies


f o r e i g n key (id-company) r e f e r e n c e s companies ( i d )
on u p d a t e no a c t i o n on d e l e t e cascade;
a l t e r t a b l e p e o p l e add c o n s t r a i n t people-fk-locations
f o r e i g n key (id-company, id-location)
r e f e r e n c e s l o c a t i o n s (id-company, i d ) ;

c r e a t e t r i g g e r people-ai f o r people
active a f t e r i n s e r t position 0
as
begin
/ * s i una persona e s e l c o n t a c t o c l a v e , elimina
e l i n d i c a d o r de todas l a s d e d s ( d e l a misma empresa) * /
if (new.key-contact = ' T' ) t h e n
u p d a t e people
set key-contact = ' P I
where id-company = new.id-company
and id <> new.id;
end;

c r e a t e t r i g g e r people-au f o r people
a c t i v e a f t e r update p o s i t i o n 0
as
begin
/ * si una persona es el contacto clave, elimina el
indicador de todas las d e d s (de la misma empresa) * /
if (new.key-contact = ' T' and old.key-contact = ' F' ) then
update people
set key-contact = ' F '
where id-company = new.id-company
and id <> new.id;
end;

Creacion de una interfaz de usuario


Las tres tablas que hemos visto hasta ahora tienen una clara relacion maestro1
detalle. Por esa razon, el ejemplo RWBlocks utiliza tres componentes IBDataSet
para acceder a 10s datos, enganchando las dos tablas secundarias a la principal.
El codigo para el soporte maestroldetalle es el del ejemplo debase de datos estandar
basado en consultas, por lo que no vamos a entrar en detalles.
Cada uno de 10s conjuntos de datos posee un conjunto completo de sentencias
SQL, para que 10s datos puedan editarse. Siempre que introducimos un nuevo
elemento de datos de detalle, el programa lo engancha a sus tablas maestro, como
en 10s dos metodos siguientes:
procedure TDmCompanies.DataLocationsAfterInsert(DataSet:
TDataSet);
begin
/ / inicia 10s datos del registro detalle
/ / con una referencia a1 registro maestro
DataLocationsID~COMPANY.AsInteger : =
DataCompaniesID.AsInteger;
end;

procedure TDmCompanies.DataPeopleAfterInsert(DataSet:
TDataSet) ;
begin
/ / inicializa 10s datos del registro detalle
// con una referencia a1 registro maestro
DataPeopleID-COMPANY.AsInteger := DataCornpaniesID.As1nteger;
/ / la ubicacion sugerida es la activa, si estd disponible
if not DataLocations.IsEmpty then
DataPeopleID-LOCATION-AsInteger : = DataLocationsID.As1nteger;
// la primera persona que se afiade se transform en el
contacto clave
// (verifica si el conjunto de datos de personas filtrado
// estd vacio)
DataPeopleKEY-C0NTACT.AsBoolean : = DataPeople.IsErnpty;
end;

Como sugiere este codigo, un modulo de datos alberga 10s componentes de


conjunto de datos. En realidad, el programa posee un modulo de datos por cada
formulario (conectado de forma dinamica, ya que podemos crear varias instancias
de cada formulario). Cada modulo tiene una transaccion independiente, para que
las diversas operaciones realizadas en distintas fichas Sean totalmente indepen-
dientes. La conexion de base de datos, en cambio, es centralizada. Un modulo de
datos principal alberga el componente correspondiente, al que hacen referencia
todos 10s conjuntos de datos. Cada uno de 10s modulos de datos lo crea de forma
dinamica el formulario haciendo referencia a el, y su valor se almacena en cl
campo privado d m del formulario:
procedure TFormCompanies.FormCreate(Sender: TObject);
begin
dm : = TDmCompanies .Create (Self);
dsCompanies.Dataset : = dm.DataCompanies;
dsLocations.Dataset : = dm.DataLocations;
dsPeople.Dataset : = dm.DataPeople;
end:

De este mod0 podemos crear facilmente varias instancias de un formulario,


con una instancia del modulo de datos conectada a cada una de ellas. El formula-
rio conectado al modulo de datos posee tres controles DBGrid, cada uno de ellos
ligado a un modulo de datos p a uno de 10s conjuntos de datos correspondientes.
Podemos ver este formulario en tiempo de ejecucion con algunos datos en la
figura 14.18.

ILI
ID I~D-COMP~ID-LOCPTIONIKEY_CONTACT~WE 1 ~ ~ 6 9 1 ~
g
I.;
I 13 3 11 T Chuck J
14 9 11 F David l I
-

Figura 14.18. Un formulario que rnuestra empresas, ubicaciones de oficinas y gente


(parte del ejemplo RWBlocks).

rn
El formulario se encuentra dentro de un formulario principal, que a su vez esta
basado en un control de pagina, que incluye otros formularios. Solo el formulario
creado con la primera pagina se crea durante el arranque del programa. El metodo
ShowForm que hemos escrito se encarga de que el formulario sea "adoptado"
por la hoja con solapa del control de ficha, despues de eliminar el borde del
formulario:
procedure TFormMain.FormCreate(Sender: TObject);
begin
ShortDateFormat : = 'dd/m/yyyyr;
ShowForm (TFormCompanies.Create (Self), TabCompanies) ;
end;

procedure TFormMain. ShowForm (Form: TForm; Tab: TTabSheet) ;


begin
Form.BorderStyle : = bsNone;
Form.Align : = alclient;
Form.Parent : = Tab;
Form.Show;
end;

En cambio, las otras dos fichas se rellenan en tiempo de ejecucion:


procedure TFormMain.PageControllChange(Sender: TObject);
begin
if PageControll.ActivePage.ControlCount = 0 then
if PageControll.ActivePage = TabFreeQ then
ShowForm (TFormFreeQuery.Create (Self), TabFreeQ)
else if PageControl1.ActivePage = TabClasses then
ShowForm (TFormClasses.Create (Self), TabClasses) ;
end;

El formulario de empresas (companies) alberga la busqueda por nombre de


empresa que ya hemos comentado, mas una busqueda por ubicacion. Escribire-
mos el nombre de una ciudad y conseguiremos una lista de empresas que tengan
una oficina en esa ciudad:
procedure TFormCompanies.btnTownClick(Sender: TObject);
begin
with dm.DataCompanies do
begin
Close;
SelectSQL.Text : =
'select c.id, c.name, c.tax-code' +
' from companies c ' +
' where exists (select loc.id from locations loc ' t
' where loc.id-company = c.id and upper (loc.town) =
r r ! +

Uppercase (edTown.Text) + ' " ) ';


Open;
dm.DataLocations.0pen;
dm.DataPeople.0pen;
end;
end ;

Si nos fijamos en el codigo fuente del formulario, veremos mucho codigo.


Parte del mismo esta relacionado con el permiso de cierre (puesto que un usuario
no puede cerrar el formulario mientras hay ediciones pendientes no enviadas a la
base de datos), mientras que otra parte considerable esta relacionada con el uso
del formulario como dialog0 de busqueda.

Reserva de clases
Parte del programa y de la base de datos esta relacionada con la reserva de
clases y cursos de formacion (aunque este programa se creo como demostracion,
tambien resulta practico.) En la base de datos existe una tabla con clases que lista
todos 10s cursos de formacion, cada uno con un titulo y una fecha fijados. Otra
tabla contiene la matricula por empresa, incluyendo las clases matriculadas, el
identificador de la empresa y algunas anotaciones. Por ultimo, una tercera tabla
contiene una lista de personas que se han apuntado, cada una de ellas conectada a
una matricula para su empresa con la cantidad pagada.
El razonamiento que se esconde tras este proceso de matriculacion basado en
empresas es que las facturas se envian a las empresas, que reservan las clases
para sus programadores y pueden recibir descuentos especificos. En este caso, la
base de datos esta un poco mas normalizada, puesto que la matricula de personas
no se refiere directamente a una clase, sino solo a la matricula de la empresa para
dicha clase. Veamos la definicion de las tablas correspondientes (se omiten las
restricciones de claves externas y otros elementos):
create table classes
(
id d-uid not null,
description varchar ( 5 0 ),
starts-on timestamp not null,
constraint classes-pk primary key ( i d )
) :
create table classes-reg
I
\

id d-uid not null,


id-company d-uid not null,
id-class d-uid not null,
notes varchar ( 25 5 ) ,
constraint classes-reg-pk primary key (id),
constraint classes-reg-uc unique (id-company, id-class)
) ;
create domain d-amount as numeric (15, 2 ) ;
create table people-reg
(
id d-uid not null,
id-classes-reg d-uid n o t n u l l ,
id-person d-uid n o t n u l l ,
amount d-amount ,
c o n s t r a i n t people-reg-pk p r i m a r y k e y ( id)
) ;

El modulo de datos para este grupo de tablas utiliza una relacion maestro1
detalleldetalle, y posee codigo para establecer la conexion con el registro maestro
activo cuando se crea un nuevo registro de detalle. Cada conjunto de datos posee
un campo generador para su identificador y cada uno tiene las sentencias SQL
update e insert apropiadas.
Dichas sentencias se han generado mediante el editor de componente corres-
pondiente usando solo el campo identificador para identificar 10s registros exis-
tentes y actualizar solo 10s campos de la tabla original. Cada uno de 10s dos
conjuntos de datos secundarios obtiene datos de una tabla de busqueda (ya sea la
lista de empresas o la lista de personas). Por ultimo, tuvimos que editar manual-
mente las sentencias Ref reshSQL para repetir la union interna adecuada. Vea-
mos un ejemplo:
object IBClassReg: TIBDataSet
Database = DmMain.IBDatabase1
Transaction = IBTransactionl
AfterInsert = IBClassRegAfterInsert
DeleteSQL-Strings = (
'delete from classes-reg'
'where id = :old-id')
InsertSQL. Strings = (
'insert into classes-reg (id, id-class, id-company, notes) '
' values (:id, :id-class, :id-company, :notes) ' )
RefreshSQL. Strings = (
' select reg. id, reg.id-class, reg.id-company, reg.notes,
c.name '
'from classes-reg reg'
'join companies c on reg.id-company = c.id'
'where id = :id' )
SelectSQL. Strings = (
'select reg. id, reg.id-class, reg.id-company, reg.notes,
c.name '
' from classes-reg reg'
'join companies c on reg.id-company = c.id'
'where id-class = :id1)
ModifySQL-Strings = (
' update classes-reg'
'set'
' id = :id,'
' id-class = :id-class,'
' id-company = :id-company,'
' notes = :notes1
'where id = :old-id' )
GeneratorField.Field = 'id'
GeneratorFie1d.Generator = 'g-master'
Datasource = dsclasses
end

Para completar esta esplicacion sobre I B C l a s s R e g , veamos su unico con-


trolador de eventos:
p r o c e d u r e TDmClasses.IBClassRegAfterInsert(DataSet: TDataSet);
begin
1BClassReg.FieldByName ('id-class').AsString :=
1BClasses.FieldByName ('idl).AsString;
end;

El conjunto de datos I B P e o p l e R e g posee parametros similares, pero el con-


junto de datos I B C l a s s e s es mas sencillo en tiempo de diseiio. En tiempo de
ejecucion, el codigo SQL de este con.junto de datos se modifica de forma dinami-
ca, usando tres alternativas para mostrar las clases programadas (siempre que la
fecha sea posterior a la actual), las clases que ya han comenzado o finalizado en
el presente aiio, y las clases de aiios anteriores. Un usuario escoge uno de 10s tres
grupos de registros para la tabla con un control de solapa, que alberga la DBGrid
de la tabla principal (vease la figura 14.19).

dd
L ID

-
19 Bc~lmdCorp
23 Wlr$echItaha Srl

21 NAME
Davd l

24 Chuck J
AMOUNT300

3W
-

--

Figura 14.19. El forrnulario de las rnatriculas de clase del ejemplo RWBlocks.

Las tres sentencias SQL alternativas se crean cuando arranca el programa, o


cuando se crea y muestra el formulario con las matriculas para las clases. El
programa almacena la parte final de las tres instrucciones alternativas (la clausu-
la where) en una lista de cadenas y selecciona una de las cadenas cuando se
cambia de solapa:
p r o c e d u r e TFormClasses.FormCreate(Sender: TObject);
begin
d m : = TDmClasses .Create (Self);
// c o n e c t a 10s c o n j u n t o s d e d a t o s a l a s f u e n t e s d e d a t o s
dsClasses.Dataset : = dm.IBClasses;
dsClassReg.DataSet : = dm.IBClassReg;
d s P e o p l e R e g - D a t a S e t : = dm.IBPeopleReg;
// a b r e 10s c o n j u n t o s d e d a t o s
dm.IBClasses.Active : = True;
dm.IBClassReg.Active : = True;
dm.IBPeop1eReg.Active : = True;

// p r e p a r a e l SOL p a r a l a s t r e s s o l a p a s
SqlComrnands : = TStringList.Create;
SqlCommands .Add ( ' w h e r e S t a r t s - O n > ' ' n o w " ' ) ;
SqlCommands.Add ( ' w h e r e S t a r t s - O n <= " n o w " and ' +
' e x t r a c t ( y e a r f r o m S t a r t s- O n ) >= e x t r a c t ( y e a r f r o m
current- t i m e stamp) ' ) ;
SqlCommands.Add ( ' w h e r e e x t r a c t ( y e a r f r o m S t a r t s - O n ) < ' +
' e x t r a c t ( y e a r from current-times tamp) ' ) ;
end;
p r o c e d u r e TFormClasses.TabChange(Sender: TObject);
begin
dm.IBC1asses.Active : = False;
dm.IBClasses.Se1ectSQL [I] : = SqlCommands [Tab.TabIndex];
dm.IBC1asses.Active : = True;
end;

Creacion de un dialogo de busqueda


Los dos conjuntos de datos de detalle de este formulario de matricula de clases
muestran algunos campos de busqueda. En lugar de mostrar el identificador de la
empresa que reservo la clase, por ejemplo, muestra el nombre de la empresa. Para
ello se usa una union interna en la sentencia SQL y se configuran las columnas de
DBGrid para que no aparezca el identificador de la empresa. En una aplicacion
local o una con una cantidad limitada de datos, podriamos haber usado un campo
de busqueda. Sin embargo, el copiar el conjunto de datos de busqueda completo
en el ambito local o el abrirlo para explorar deberian limitarse a las tablas con
unos cien registros aproximadamente, incluyendo algunas capacidades de bus-
queda.
Si tenemos una tabla grande, como una tabla de empresas, una solucion alter-
nativa puede ser utilizar un cuadro de dialogo secundario para realizar la selec-
cion de busqueda. Por ejemplo, podemos escoger una empresa usando el formulario
que ya hemos creado y aprovechando sus capacidades de busqueda. Para mostrar
dicho formulario como un cuadro de dialogo, el programa crea una nueva instan-
cia del mismo, muestra algunos botones ocultos que ya estaban ahi en tiempo de
diseiio y permite que el usuario seleccione una empresa a la que hacer referencia
desde otra tabla. Para simplificar el uso de esta busqueda, que puede darse varias
veces en un programa grande, hemos aiiadido a1 formulario de empresas una
funcion de clase, que tiene como parametros de salida el nombre e identificador de
la empresa seleccionada. Se puede pasar un identificador inicial a la funcion para
determinar su seleccion inicial. Veamos el codigo completo de esta funcion de
clase, que crea un objeto de su clase, selecciona el registro inicial si asi se solicita,
muestra el cuadro de dialog0 y, por ultimo, extrae 10s valores de retorno:
class function TFormCompanies.SelectCompany (
var CompanyName: string; var CompanyId: Integer): Boolean;
var
FormComp : TFormCompanies ;
begin
Result : = False;
FormComp : = TFormCompanies-Create (Application);
FormComp.Caption : = ' S e l e c t Company1;
try
// a c t i v a 10s botones d e didlogo
FormComp.btnCancel.Visib1e : = True;
FormComp.btn0K.Visible : = True;
// s e l e c c i o n a empresa
if CompanyId > 0 then
FormComp.dm.DataCompanies.SelectSQL.Text :=
' s e l e c t c . i d , c . name, c . t a x - c o d e ' +
' from companies c ' +
' w h e r e c . i d = ' + IntToStr (CompanyId)
else
FormComp.dm.DataCompanies.Se~ectSQL.Text : =
' s e l e c t c . i d , c . name, c . t a x - c o d e ' +
' from companies c ' +
' w h e r e name-upper s t a r t i n g w i t h ' ' a " ' ;
FormComp.dm.DataCompanies.Open;
FormComp.dm.DataLocations.0pen;
FormComp.dm.DataPeople.Open;

if FormComp. ShowModal = mrOK then


begin
Result : = True;
CompanyId : = FormComp.dm.DataCompanies.Fie1dByName
( ' i d ' ) .AsInteger;
CompanyName : = FormComp.dm.DataCompanies.Fie1dByName
( ' n a m e ' ) .Asstring;
end ;
finally
FormComp-Free;
end ;
end;

Otra funcion de clase ligeramente mas compleja (disponible en el codigo fuen-


te del ejemplo, per0 que no aparece listada aqui) permite seleccionar a una perso-
na de una empresa dada para matricular a personas en las clases. En ese caso, el
formulario se muestra despues de desactivar la busqueda de otra empresa o la
modificacion de 10s datos de esa empresa.
En ambos casos, la busqueda se desencadena aiiadiendo un boton de puntos
suspensivos a la columna de la DBGrid (por ejemplo, la columna de la cuadricula
que lista 10s nombres de las empresas matriculadas para las clases). Cuando se
pulsa este boton, el programa llama a la funcion de clase para mostrar el cuadro
de dialog0 y utiliza su resultado para actualizar el campo identificador oculto y el
campo de nombre visible:
p r o c e d u r e TFormClasses.DBGridClassRegEditButtonClick(Sender:
TObject) ;
var
CompanyName : string;
CompanyId: Integer;
begin
CompanyId : = dm.IBClassReg.Fie1dByName
( ' id- Company') .AsInteger;
i f TFormCompanies.SelectCompany (CompanyName, CompanyId)
then
begin
dm.1BClassReg.Edit;
dm.IBClassReg.Fie1dByName ('Name').Asstring : = CompanyName;
dm. IBClassReg. FieldByName ( ' id-Company' ) .AsInteger :=
CompanyId;
end;
end ;

Adicion de un formulario de consulta libre


La caracteristica final del programa es un formulario en el que un usuario
puede escribir directamente y ejecutar una sentencia SQL. Como ayuda adicional,
el formulario lista en un cuadro combinado las distintas tablas disponibles de la
base de datos. obtenidas cuando se crea el formulario a1 llamar a:

A1 seleccionar un elemento del cuadro combinado, se genera una sencilla con-


sulta SQL:
MemoSql.Lines.Text := 'select * f r o m ' + ComboTables.Text;

El usuario, si es un experto, puede editar entonces la sentencia SQL, introdu-


ciendo posiblemente clausulas restrictivas y ejecutando, a continuacion, la con-
sulta:
p r o c e d u r e TFormFreeQuery.ButtonRunClick(Sender: TObject);
begin
QueryFree .Close;
QueryFree.SQL : = MemoSql.Lines;
QueryFree.Open;
end;

Podemos ver este tercer formulario del programa RWBlocks en la figura 14.20.
Por supuesto que no sugerimos que se afiada edicion SQL a 10s programas pensa-
dos para todos 10s usuarios. Esta caracteristica basicamente se destina a usuarios
avanzados o programadores.

.....".__-... , , , , , , , -,
selecl ' lrom classes

--

~ID ~DESCRIPTION I STARTS-ON I


b 18 Lea~mcqXML 10/l0/2002

Figura 14.20. El formulario de consulta libre del ejernplo RWBlocks esta pensado
para usuarios avanzados.
Trabajo
con ADO

Desde mediados de 10s aiios 80, 10s programadores de bases de datos han
buscado el "santo grial" de la independencia de las bases de datos. La idea es
utilizar una API unica que puedan usar las aplicaciones para interactuar con
muchas fuentes de datos distintas. El uso de una API de este tip0 liberaria a 10s
desarrolladores de la dependencia con respecto a un unico motor de bases de
datos y les permitiria adaptarse a las necesidades en constante carnbio de todo el
mundo.
Los fabricantes han producido muchas soluciones para este objetivo, siendo
las dos mas notables Open Database Connectivity (ODBC) de Microsoft y la
Integrated Database Application Programming Interface (IDAPI) de Borland,
que es mas conocida como Borland Database Engine (BDE).
Microsoft comenzo a sustituir ODBC con OLE DB a mediados de 10s aiios 90,
con el exito de COM. Sin embargo, OLE DB es lo que lo que Microsoft clasifica-
ria como una interfaz a nivel de sistema y esta pensada para 10s programadores a
nivel de sistema. Es una solucion muy grande, compleja y exquisita. Requiere un
programador habil y capaz y un alto grado de conocimiento, a carnbio de una
productividad muy baja. ActiveX Data Objects (ADO) es una capa que recubre a
OLE DB y suele definirse como una interfaz a nivel de aplicacion. Es considera-
blemente mas simple que OLE DB y mas relajada. En pocas palabras, esta dise-
iiada para 10s programadores de aplicaciones.
Como se trato en el capitulo 14; Borland tambien ha sustituido a BDE por una
tecnologia mas reciente. llamada dbExpress. ADO tiene mas parecidos con BDE
que con la tecnologia ligera de dbExpress. BDE y ADO soportan la navegacion y
manipulacion de conjuntos de datos, el procesamiento de transacciones y las ac-
tualizaciones en cache (llamadas actualizaciones por lotes en ADO). de manera
que 10s conceptos relacionados con el uso de ADO son similares a 10s de BDE.

NOTA: Me gustaria reconocer y agradecer la labor de Guy Smith-Ferrier


vor escribir oriainalmente este cavitulo Dara la edicion anterior de este

The Delphi Magazine y para otros temas no relacionados con Delphi. Tam-
bien ha participado en numerosas conferencias en Norte America y en Eu-
ropa. Guy vive en Inglaterra con su mujer, su hijo y su gato.

En este capitulo prestaremos nuestra atencion a ADO. Tambien analizaremos


dbGo, un conjunto de componentes Delphi llamado inicialmente ADOEspress,
per0 que se renombro en Delphi 6 ya que Microsoft se opone a1 uso del termino
ADO en nombres de productos de terceros. Es posible usar ADO en Delphi sin
necesidad de recurrir a dbGo. A1 importar la biblioteca de tipos de ADO, se
consigue acceso direct0 a las interfaces de ADO; es asi como 10s programadores
en Delphi usaban ADO antes de la version 5 de Delphi. Sin embargo, de esta
manera nos saltamos la infraestructura de bases de datos de Delphi y garantiza-
mos la imposibilidad de usar otras tecnologias de Delphi como 10s controlesdata-
aware o Datasnap. Este capitulo utiliza dbGo para todos sus ejemplos, no solo su
inmediata disponibilidad y soporte, sino tambien porque se trata de una solucion
muy viable. Sin importar cual sea la opcion final tomada, esta informacion resul-
tara muy util.

NOTA: Ademh, se puede acceder a conjuntos de componentes ADO para


Delphi de terceros como Adonis, AdoSolutio, Diamond ADO y Karniak.

En este capitulo trataremos 10s siguientes temas:


Microsoft Data Access Components (MDAC).
dbGo de Delphi.
Archivos de enlace de datos.
Adquisicion de informacion esquematica.
Uso del motor Jet.
Procesamiento de transacciones.
Conjuntos de registros desconectados y persistentes.
El modelo de maletin y el despliegue de MDAC

Microsoft Data Access Cornponentes (MDAC)


ADO es una parte del panorama mas amplio de 10s Microsoft Data Access
Componentes (MDAC), 10s componentes de acceso a datos de Microsoft. MDAC
es un paraguas para las tecnologias de bases de datos de Microsoft, e incluye
ADO, OLE DB, ODBC y RDS (Remote Data Services). Se suele oir hablar a la
gente, incorrectamente, de 10s terminos MDAC y ADO como si fueran intercam-
biables. Ya que ADO solo se distribuye como parte de MDAC, hablaremos de las
versiones de ADO en terminos de distribuciones de MDAC. Las principales dis-
tribuciones de MDAC han sido las versiones 1.5, 2.0, 2.1, 2.5 y 2.6. Microsoft
distribuye MDAC de forma independiente y lo ofrece en forma de descarga gra-
tuita y de distribucion virtualmente gratuita (existen requisitos de distribucion,
per0 la mayor parte de 10s desarrolladores en Delphi no tendran problemas para
cumplirlos). MDAC se distribuye tambien con la mayoria de 10s productos de
Microsoft que tengan contenido en bases de datos. Delphi 7 se distribuye con
MDAC 2.6. De este nivel de disponibilidad se desprenden dos consecuencias. En
primer lugar, es muy probable que 10s usuarios ya tengan instalado MDAC en sus
maquinas. En segundo lugar, sin importar la version que tengan 10s usuarios (o a
la que 10s actualicemos) tambien es virtualmente cierto que alguien (el
desarrollador, 10s usuarios u otro software de aplicacion) actualicen el MDAC ya
instalado con la version mas actual. No se puede impedir esta actualizacion, ya
que MDAC lo instala software tan comun como Internet Explorer. Si aiiadimos a
esto que Microsoft solo soporta la version actual de MDAC y la inmediatamente
anterior, se llegara a esta conclusion: hay que diseiiar las aplicaciones para fun-
cionar con la version actual de MDAC o la anterior.
Como desarrollador que usa ADO, deberian visitarse regularmente las paginas
sobre MDAC en el sitio Web de Microsoft, en www.microsoft.com/data. Desde
ahi se podra descargar gratuitamente la ultima version de MDAC. Mientras se
visita este sitio Web, deberia aprovecharse la oportunidad para descargar el SDK
de MDAC (13 MB) si aun no se tiene, o el Platform SDK (que incluye el SDK de
MDAC). El SDK de MDAC sera como la biblia: descarguelo, consultelo regular-
mente y uselo para solucionar todas sus dudas sobre ADO. Deberia tratarse tam-
bien como la primera fuente de ayuda cuando se necesite informacion sobre MDAC.

Proveedores de OLE DB
Los proveedores de OLE DB permiten acceder a una fuente de datos. Se trata
de 10s homologos de ADO a 10s controladores dbExpress y 10s SQL Links de
BDE. Cuando se instala MDAC, se instalan automaticamente 10s proveedores
OLE DB que muestra la tabla 15.1

Tabla 15.1. Proveedores OLE DB incluidos con MDAC.

MSDASQL Controladores ODBC Controladores ODBC (prede-


terrninados)
Microsoft.Jet.OLEDB.3.5 Jet 3.5 Solo acceso a bases de da-
tos de MS Access 97
Microsoft.Jet.OLEDB.4.0 Jet 4.0 MS Access y otras bases de
datos
SQLOLEDB SQL Server Bases de datos de MS SQL
Server
MSDAORA Oracle Bases de datos de Oracle
MSOLAP Servicios OLAP Online Analytical Processing
SarnpProv Proveedor de muestra Ejernplo d e un proveedor
OLE DB para archivos CSV
MSDAOSP Proveedor sencillo Para crear proveedores pro-
pios para datos de texto sirn-
pie

El proveedor de OLE DB ODBC se usa por compatibilidad regresiva con


ODBC. A medida que se aprenda mas sobre ADO, se descubriran 10s
limites de este proveedor.
Los proveedores OLE DB Jet soporta MS Access y otras bases de datos de
sistemas de escritorio. Los trataremos mas adelante.
El proveedor SQL Server soportar SQL Server 7, SQL Server 2000 y
Microsoft Database Engine (MSDE). MSDE es una version reducida de
SQL Server, sin la mayoria de las herramientas y algo de codigo aiiadido
para degradar intencionadamente el rendimiento cuando existan mas de
cinco conexiones activas. MSDE es importante porque es gratuito y com-
pletamente compatible con SQL Server.
El proveedor OLE DB para OLAP puede usarse directamente, per0 suele
usarlo mas a menudo ADO Multi-Dimensional (ADOMD). ADOMD es
una tecnologia ADO adicional diseiiada para ofrecer procesamiento ana-
litico en red (Online Analytical Processing, OLAP). Si se ha usado alguna
vez Decision Cube de Delphi, Pivot Tables de Excel o Cross Tabs de
Access, entonces se habra usado algun tipo de OLAP.
Ademas de estos proveedores OLE DB de MDAC, Microsoft ofrece otros pro-
veedores con otros productos o con equipos de desarrollo que pueden descargarse:
El proveedor OLE DB deActive Directory Services se incluye con el SDK
de ADSI: el proveedor OLE DB para AS1400 y VSAM se incluye con el
servidor SNA Server; y el proveedor OLE DB para Exchange se incluye
con Microsoft Exchange 2000.
El proveedor OLE DB para Indexing Service es parte del Microsoft Indexing
Service, un mecanismo de Windows que acelera las busquedas de archivos
a1 crear catalogos de informacion sobre archivos. Indexing Service esta
intcgrado con IIS y, en consecuencia, suele usarse para crear indices de
sitios Web.
El proveedor OLE DB para Internet Publishing permite que 10s
desarrolladores manipules directorios y archivos mediante HTTP.
Hay aim mas proveedores OLE DB en forma de proveedores de servicios.
Como implica su nombrc, 10s proveedores de servicio OLE DB ofrecen un
servicio a otros proveedores OLE DB y suelen invocarse automaticamente
cuando se necesita; sin la intervention del programador. El Cursor Service,
por ejemplo, se invoca cuando se crea un cursor de cliente, y el proveedor
Persisted Recordset se invoca para guardar localmente datos.
MDAC incluye muchos proveedores que analizaremos con detalle, pero hay
muchos mas disponibles gracias a Microsoft y a un importante mercado de terce-
ros. Es imposible proporcionar una lista precisa de todos 10s proveedores OLE
DB disponibles, ya que esta lista es muy grande y cambia constantemente. Ade-
mas de terceros independientes, deberian tenerse en cuenta a la mayoria de 10s
dcsarrolladorcs de bases de datos. ya que suelen proporcionar sus propios provee-
dores OLE DB. Por ejcmplo, Oracle ofrece el proveedor ORAOLEDB.

TRUCO: Un notable olvido de 10s fabricantes que ofrecen proveedores


OLE DB es InterBase. Ademas de acceder a InterBase mediante el contro-
lador ODBC, se puede usar el IBProvider de Dmitry Kovalenko (www.lcpi.
lipetsk.~/prog/eng/index.html). Puede probarse tarnbih el OLE DB Provider
Development Toolkit de Binh Ly (www.techvanguards.comlproducts/optk/
install.htm): Si se desea escribir un proveedor OLE DB propio, esta herra-
mienta es m i s facil de usar que casi cualquier otra.

Uso de componentes dbGo


Los programadores familiarizados con BDE, dbExpress o IBExpress deberian
reconocer el con.junto de componentes que conforman dbGo (vease la tabla 15.2).
Tabla 15.2. Componentes dbGo.

ADOConnection Conexion a una base Database


de datos
ADOCornrnand Ejecuta un comando Sin equivalente
SQL de accion
ADODataSet Descendiente de Sin equivalente
TDataSet de proposito
general
ADOTable Encapsulacion de una Table
tabla
ADOQuery Encapsulacion de la Query
sentencia SELECT de
SQL
ADOStoredProIC Encapsulacion de un
procedirniento alrnacenado StoredProc
RDSConnection Conexion a Remote Data Sin equivalente
Services

Los cuatro componentes de conjuntos de datos (ADODataSet, ADOTable,


ADOQuery y ADOStoredProc) estan casi completamente implementados por su
clase padre inrnediata, TCustomADODataSet. Este componente proporciona la
mayor parte de la funcionalidad del conjunto de datos, y sus descendentes son en
general envoltorios ligeros que exponen distintas caracteristicas del mismo com-
ponente. Como tales, 10s componentes tienen mucho en comun. Sin embargo, en
general ADOTable, ADOQuery y ADOStoredProc se contemplan como compo-
nentes de "compatibilidad" y se usan para facilitar la curva de aprendizaje y de
codigo desde sus homologos BDE. Un aviso: estos componentes de compatibili-
dad son parecidos a sus homologos, per0 no identicos. Se encontraran diferencias
en cualquier aplicacion except0 en las m h triviales. ADODataSet es el compo-
nente a escoger, en parte debido a su versatilidad, per0 tambien a su mayor pare-
cido con la interfaz Recordset de ADO en la que se basa. A lo largo de este
capitulo, usaremos todos 10s componentes de conjunto de datos para dar una idea
sobre como usar cada uno.

Un ejemplo practico
Ya basta de teoria: veamos como funcionan las cosas. Colocamos un ADOTable
en un formulario. Para indicar la base de datos a la que conectarse, ADO usa
cadenas de conexion. Se puede escribir una cadena de conexion manualmente si
se sabe lo que se esta haciendo. En general, se usara un editor de cadenas de
conexion (el editor de propiedad para la propiedad C o n n e c t i o n s t r i n g ) , que
muestra la figura 15.1.

Figura 15.1. El editor de cadenas de conexion de Delphi

Este editor aiiade poca cosa al proceso de escribir una cadena de conexion, por
lo que se puede hacer clic sobre el boton Build para usar directamente el editor de
cadenas de conesion de Microsoft, que se muestra en la figura 15.2.
Se trata de una herramienta que es importante conocer. La primera pestaiia
muestra 10s proveedores OLE DB y de servicio instalados en el ordenador. La
lista variara segun la version de MDAC y de otro software instalado. En este
ejemplo, seleccionaremos el proveedor OLE DB Jet 4.0. Hacemos doble clic so-
bre Jet 4.0 OLE Dl3 Provider y aparecera la pestafia Connection. Esta pagina
varia segun el proveedor seleccionado; para Jet, solicita el nombre de la base de
datos y 10s detalles del usuario de conexion. Se puede acceder a un archivo MDB
de Access instalado por Borland junto con Delphi 7: el archivo dbdemos.mdb
disponible en la carpeta compartida de datos (de manera predefinida, C:\Archivos
de programa\Archivos comunes\Borland Shared\Data\dbdemos.mdb). Haremos
clic sobre el boton Probar Conexi6n para dar validez a las opciones selecciona-
das. La pestafia Avanzadas maneja el control de acceso a la base de datos; aqui
se especifica el acceso exclusivo o de solo lectura a la base de datos. La pestaiia
Todas muestra una lista de todos 10s parametros de la cadena de conexion. La
lista es especifica del proveedor OLE DB que se haya escogido al principio.
(Deberiamos fijarnos con atencion en esta pagina, ya que contiene muchos
parametros que son la respuesta a muchos problemas.) Despues de cerrar el editor
de cadenas de conexion de Microsoft, ser vera en el editor de Borland para la
propiedad C o n n e c t i o n s t r i n g el valor devuelto a esta propiedad (que aqui
se muestra en varias lineas por comodidad):
Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=C:\Archivos d e programa\Archivos comunes\Borland
Shared\Data\dbdemos.mdb;
Persist Security Info=False

Las cadenas de conexion son simples cadenas con muchos parametros separa-
dos por punto y coma. Para aiiadir, modificar o eliminar cualquiera de estos
parametros mediante programacion, hay que escribir rutinas propias para encon-
trar el parametro en la lista y modificarlo como corresponda. Un enfoque mas
simple es copiar la cadena en una lista de cadenas de Delphi y usar su prestacion
de pares nombrelvalor: esta tecnica se mostrara en el ejemplo JetText que vere-
mos mas adelante.

Sdbfciorab a ddos a br quc desea camtarre: I


Prw&e&OLE DB .- -_
Med~aCsIslogDBOLE DB Prowdm
MedtaLataloaMeroedDB OLE DB Prowder
~ e d l a ~ d a l o g OLE
~ e bDB
~ Plov~der
~
M~crosoltISAM 1 1 OLE DB Prov~der
M~uosolrJet4 0 OLE DB Prov~der
M~c~osolt OLE DB Prov~dmFa D a t a M m g S m c r
Mtc~osoltOLE DB Provtder lo1 lndevmg Suvlce
M~nosoltOLE 00 Plowdm b~ htslnel Pubhshtr~~

M~crosoltOLE DB Provder lor OLAP Serv~ces


M~crosoltOLE DB PrtJndel for OLAP Servlces 8 0

I*, !
Mtcrosolt OLE D8 Prov~de~for O~acle
Mlcrosolt OLE D8 Prowder for Outlook Search
Mlcrosolt OLE DB Pronder for SOL Serva
Mlcrosoft OLE DB S~mpleP ~ o w d e ~
MSDataShape
,

Figura 15.2. La primera pagina del editor de cadenas de conexion de Microsoft.

Ahora que se ha determinado la cadena de conesion, se puede escoger una


tabla. Desplegaremos la lista de tablas usando la propiedad TableName en el
Object Inspector. Seleccionados la tabla Customer. Aiiadimos un componente
DataSource y un control DBGrid, y 10s conectamos entre si; ahora vamos a usar
ADO en un programa real (aunque trivial), disponible como ejemplo
FirstAdoExample. Para ver 10s datos, se establece la propiedad A c t i v e del
conjunto de datos como True, o se abre el conjunto de datos en el evento
Formcreate (corno en el ejemplo), para evitar errores en tiempo de diseiio si no
se encuentra disponible la base de datos.
L . -
TRUCO: Si se va a utilizzkr dbGo como la principal tecnologia de a c e so a
-
--._--
bases de datos, podria descm s e llevar el componente DataSource a la pi~gi-
na ADO de la Component ra~errepara ey1ra.r
-1 .. . . entre la
tener que cambia- .
piginaAb0 y iap&ha Data Access. Si se utilizan tanto ADO como otra
tecaologia de bases de dabs, se puede simular la instalacibn de DataSource
en varias p & g hmw-m plarrtilla de componente para un DataSource
e ih&&ndola en la pkgina ADO.
El componente ADOConnection
Cuando se usa de este mod0 un componente ADOTable, crea su propio com-
ponente interno de conexion. No es necesario aceptar la conexion predeterminada
que crea. En general, deberia crearse una conexion propia mediante el componen-
t e ADOConnection, que tiene el mismo proposito que el componente
SQLConnection de dbExpress y el componente Database de DBE. Permite perso-
nalizar el procedimiento de entrada en el sistema, controlar transacciones, ejecu-
tar directamente comandos de accion y reducir el numero de conexion de una
aplicacion.
Usar un ADOConnection es sencillo. Hay que colocar uno en un formulario y
fijar su propiedad C o n n e c t i o n s t r i n g del mismo mod0 que se haria para el
componente ADOTable. Alternativamente, se puede hacer doble clic sobre un
componente ADOConnection (o usar un elemento especifico de su Componente
Editor, en su menu local) para llamar directamente a1 editor de la cadena de
conexion. Con C o n n e c t i o n s t r i n g apuntando a la base de datos correcta, se
puede inhabilitar el cuadro de dialog0 de entrada fijando como F a l s e la propie-
dad L o g i n P r o m p t . Para usar una nueva conexion en el ejemplo anterior, hay
que establecer la propiedadconnection deADoTable1 aADOConnection1.
Se vera que la propiedad C o n n e c t i onS t r i ng de A D O T a b l e 1 recupera su
valor original.
Esto se debe a que las propiedades c o n n e c t i o n y C o n n e c t i o n s t r i n g
son mutuamente excluyentes. Una de las ventajas de usar un ADOConnection es
que la cadena de conexion esta centralizada en lugar de repartida a lo largo de
muchos componentes.
Otra ventaja, mas importante, es que todos 10s componentes que comparten el
componente ADOConnection comparten una conexion unica con el servidor de la
base de datos. Sin su propia ADOConnection, cada conjunto de datos ADO ten-
dria una conexion independiente.

Archivos de enlace de datos


Asi, un ADOConnection permite centralizar la definicion de una cadena de
conexion dentro de un formulario o modulo de datos. Sin embargo, aunque esto
supone un importante paso adelante con respecto a repartir la misma cadena de
conexion entre todos 10s conjuntos de datos ADO, tiene un fa110 esencial: si se
utiliza un motor de bases de datos que defina la base de datos en terminos de
nombre de archivo, entonces la ruta a 10s archivos de la base de datos estaran
grabados en el archivo ejecutable. Esto hace que la aplicacion resulte fragil. Para
superar este problema, ADO usa 10s archivos de enlace de datos (o Data Link).
Un archivo de enlace de datos es una cadena de conexion en un archivo INI.
Por ejemplo, la instalacion de Delphi aiiade a1 sistema el archivo dbdemos .u d l ,
con el siguiente texto:
[oledb]
; Everything after this line is an OLE DB initstring
Provider=Microsoft.Jet.OLEDB.4.0;
Data Source=C:\Archivos d e programa\Archivos comunes\Borland
Shared\Data\dbdemos.mdb

Aunque puede darse cualquier extension aun archivo de enlace de datos, la


extension recomendad es .U D L . Se puede crear un enlace de datos mediante
cualquier editor de texto, o se puede hacer clic con el boton derecho sobre el
Explorador de Windows, escoger Nuevo~Documentode texto, renombrar el
archivo con una extension .U D L (suponiendo que se muestren las extension con
la configuracion del Explorador que se utilice), y despues hacer doble clic sobre
el archivo para llamar a1 editor de cadenas de conexion de Microsoft.
Cuando se escoge un archivo en el editor de conexion, la propiedad
C o n n e c t i o n s t r i ng tomara el valor 'FILE NAME=', seguido del nombre real
del archivo, como muestra el ejemplo DataLinkFile. Se pueden colocar 10s archi-
vos de enlace o vinculo de datos en cualquier parte del disco duro, per0 si se busca
una ubicacion habitual y compartida, se puede usar la funcion D a t a L i n k D i r
de la unidad ADODB de Delphi. Si no se ha modificado la configuracion prede-
terminada de MDAC, D a t a L i n k D i r devolvera lo siguiente:
C:\Archivos de programa\Archivos comunes\System\OLE DB\Data Links

Propiedades dinamicas
Imaginemos que somos 10s responsables del diseiio de una nueva arquitectura
de software intermedio (middleware) de bases de datos. Tenemos que reconciliar
10s dos objetivos antagonicos de conseguir una sola API para todas las bases de
datos y acceder a funciones especificas de todas las bases de datos. ADO tiene
que resolver estos objetivos que a1 parecer se excluyen mutuamente, y lo hace
utilizando propiedades dinamicas. Casi todas las interfaces ADO, y sus corres-
pondientes componentes dbGo, tienen una propiedad denominada p r o p e r t i e s
que es un conjunto de propiedades especificas de cada base de datos. Se puede
acceder a estas propiedades mediante su posicion ordinal, del siguiente modo:
ShowMessage (ADOTable1.Properties [I] .Value) ;

Pero normalmente se accede a ellas por nombre, del siguiente modo:


ShowMessage (ADOConnectionl. Properties [ 'DBMS Name' ] .Value) ;

Las propiedades dinamicas dependen del tip0 de objeto y tambien de 10s pro-
veedores OLE DB. Para hacernos una idea de su importancia, una conexion ADO
tipica o un conjunto de registros tiene aproximadamente 100 propiedades dinami-
cas. Como se vera a lo largo de este capitulo, las respuestas a muchas preguntas
sobre ADO tienen que ver con las propiedades dinamicas.
TRUCO: Un evento importante relacionado con el uso de propiedades di-
n M c a s es OnRecordsetCreate, que se introdujo en una actualiza-
cion de Delphi 6 y est&disponible en Delphi7. OnRecordse tcreate se
usa inmediatamente despuCs de que se haya creado un conjunto de regis-
tros, pero antes de que se haya abierto. Esto resulta util para definir algu-
nas propiedades d i n h i c a s puesto que algunas de ellas s6lo se pueden definir
cuando el conjunto de registro estA cerrado.

Obtencion de informacion esquematica


En ADO, se puede conseguir informacion esquematica mediante el metodo
OpenSchema del componente ADOConnection. Este metodo acepta cuatro
parametros:

El tip0 de datos que deberia devolver OpenSchema. Se trata de un valor


TSchemaInfo, que es un conjunto de 40 valores entre 10s que se encuen-
tran aquellos necesarios para recuperar una lista de tablas, indices, colum-
nas, vistas y procedimientos almacenados.
Un filtro que se va a colocar en 10s datos antes de que estos se devuel-
van. Se vera un ejemplo de este parametro en breve.
Un GUID para una consulta especifica de proveedor. Solo se usa si el
primer parametro es siProviderSpecif ic.
Un ADODataSet en el que se devuelven 10s datos. Este ultimo parametro
muestra un tema comun en ADO: cualquier metodo que tenga que devolver
una gran cantidad de datos 10s devolvera en forma de Recordset (conjunto
de registros), o, en terminos de Delphi, un ADODataSet.
Para usar OpenSchema,es necesario abrir una ADOConnection. El ejemplo
siguiente (parte del ejemplo OpenSchema) recupera una lista de claves primarias
para cada tabla de un ADODataSet:
ADOConnectionl.OpenSchema(siPrimaryKeys, EmptyParam,
EmptyParam, ADODataSetl) ;

Cada campo de una clave primaria posee una fila unica en el conjunto de
resultados. Asi, una tabla con una clave compuesta por dos campos tiene dos
filas. Los dos valores EmptyParam indican que dichos parametros estan vacios
y se ignoran. El resultado de este codigo se muestra en la figura 15.3, despues de
ajustar el tamaiio de la cuadricula con algo de codigo personalizado.
Cuando se pasa EmptyParam como segundo parametro, el conjunto de re-
sultados incluye toda la informacion del tipo solicitado para toda la base de datos.
Para muchos tipos de informacion, querremos filtrar el conjunto de resultado. Por
supuesto que podemos aplicar un filtro tradicional de Delphi a1 conjunto de resul-
tad0 usando las propiedades F i l t e r y F i l t e r e d o el evento O n F i l t e r -
R e c o r d . Sin embargo, esto aplica el filtro en la parte de cliente de este ejemplo.
Usando el segundo parametro, podemos aplicar un filtro mas eficaz en la fuente
de la informacion esquematica. El filtro se especifica como una matriz de valores.
Cada elemento de la matriz posee un significado especifico importante para el
tipo de datos que se van a devolver. Por ejemplo, la matriz de filtro para claves
primarias posee tres elementos: El primer0 es el catalogo (catalogo es el tkrmino
ANSI para base de datos), el segundo es el esquema, y el tercero es el nombre de
la tabla. Este ejemplo devuelve una lista de claves primarias para la tabla Customer:
var
Filter: OLEVariant;
begin
Filter : = VarArrayCreate ( [0, 2 1 , varvariant) ;
Filter [ Z ] : = 'CUSTOMER';
ADOConnectionl.OpenScherna(
siPrimaryKeys, Filter, Emptyparam, ADODataSetl);
end;

country Name
customer CustNo
emdo~ee EmpNo
tlems ItemNo
ems 01derNo
MSysAccessOblecls 10
orders OrhNo
parts Pa~tNo
vendas VendorNo

Figura 15.3. El ejemplo Openschema obtiene las claves primarias de las tablas de la
base de datos.

- -- - -- -- --
- - - - - - ----- - -
-
N,- de paede dbtener @ xqi~ma$nfoma&m ufilizanda-ADQX, ADOX
9 ecnologia A ~ adicianal
O qpepemite obtener y actualizm m&ma-
ci6n esi$uematica. Es el ,egu'rv@en'te en N O a1 lenguaje de desdi&~de
de SQL (DofaD e f i n i t i ~ n L n n ~ o g e , !con
~ L las
) , sentenciasCREWI2'~.
mTEIR, DROP. y al lenguaje t.!C ,eor#roI da-datos (DafaCo~ltrdlh?guage,
#Kt;) . GRANT y REVOKE. dbGo no soporta directamente ADOX, per6
con
puede importarse la biblioteca de tipos ADOX y usarla con exito en aplica-.
universal como OpenScherna, asi que hay muchos huews sin cubrir. Pafa
simplemente obtener la information esquemhtica y no actualizarla,
Openschema suele ser una opcion mejor.

Uso del motor Jet


Ahora que conocemos algunos de 10s conceptos basicos sobre MDAC y ADO,
es hora de centrarnos en las caracteristicas del motor Jet. Este motor es muy
interesante para algunos, y nada para otros. Para 10s interesados en Access,
Paradox, dBase, testo, Excel, Lotus 1-2-3 o HTML, este apartado resultara muy
util. Si no interesa ninguno de estos formatos, se puede ignorar esta seccion.
El motor de bases de datos Jet se asocia normalmente con las bases de datos
Microsoft Access y este es sin duda su punto fuerte. Sin embargo, el motor Jet
tambien es un motor de bases de datos de escritorio con diversas funciones y es en
esta caracteristica menos conocida donde reside su verdadera potencia. Ya que
usar el motor Jet con Access es el mod0 predefinido y mas sencillo, esta seccion
trata cn su mayor parte 10s formatos distintos de Access, que no resultan tan
obvios.

NOTA: El motor Jet se incluia con MDAC en algunas versiones (pero no


en todas). No se incluye en la versidn 2.6 de MDAC v2.6. Ha habido un
gran debate sobre si 10s programadores que usan una herramienta de desa-
rrollo que no es de Microsoff tienen derecho a distribuir el mator Jet. La
respuesta oficial es positiva, y el motor Jkt se encuentra disponible como
descarga gratuita (ad& de distribuirse con muchos productos de soft-
ware de Micros&]. .-

Existen dos proveedores OLE DB de Jet: el proveedor Jet 3.5 1 OLE DB y el


proveedor Jet 4.0 OLE DB. Jet 3.5 1 OLE DB usa el motor Jet 3.5 1 y solo soporta
bases de datos Access 97. Si queremos usar Access 97 y no Access 2000, mejora-
remos el funcionamiento usando este proveedor OLE DB en la mayoria de 10s
casos en lugar del proveedor Jet 4.0 OLE DB.
Jet 4.0 OLE DB soporta Access 97, Access 2000 y controladores de Installable
Indesed Sequential Access Method (IISAM). Los controladores Installable ISAM
son aquellos escritos especificamente para que el motor Jet soporte acceso a
formatos ISAM tales como Paradox, dBase y texto, y es esta capacidad la que
conviertc a1 motor Jet en una herramienta tan util y versatil. La lista completa de
controladores ISAM instalados en nuestro equipo depende del software que haya-
mos instalado en el mismo. Podemos encontrar dicha lista en el Registro, en:
H K E Y ~ L O C A L ~ ~ C H I N E \ S o f t w a r e \ M i c r o s o f t \ J e t \ 4 . O \ I S A Formats
M
Sin embargo, el motor Jet incluye controladores para Paradox, dBase, Excel,
texto y HTML.

Paradox a traves de Jet


Se supone que el motor Jet es para utilizar bases de datos Access. Para usarlo
con cualquier otra base de datos distinta de Access, es necesario indicarle que
controlador IISAM habra de utilizar. Se trata de un procedimiento muy sencillo
que implica definir el argument0 de cadena de conexion Extended Properties en el
editor de cadenas de conexion.
Por ejemplo, aiiadimos un componente ADOTable a un formulario y recurri-
mos al editor de cadenas de conexion. Seleccionamos el Jet 4.0 OLE DB Provider.
Seleccionamos la ficha Todas, localizamos la propiedad Extended Properties y
hacemos doble clic sobre la misma para editar su valor.
Escribimos Paradox 7.x como valor de la propiedad, como muestra la figura
15.4, y hacemos clic sobre el boton Aceptar. Volvemos ahora a la pestaiia Co-
nexidn y escribimos el nombre del directorio que contiene las tablas Paradox
directamente, porque el boton de navegacion (de puntos suspensivos) no servira
de mucho (permite escribir un nombre de archivo, no un nombre de carpeta). En
este punto, podemos escoger una tabla en la propiedad TableName del compo-
nente ADOTable y abrirla ya sea en tiempo de diseiio o de ejecucion. Estaremos
usando Paradox a traves de ADO, como muestra el ejemplo Jetparadox.

trlss mlor p?cpkbhs dc &idnacibn de ede b o de data


Pmqmaddica m v*. deccim 1.1.wledad Y. a
e d n . &M o e r a v e h

N n b e _ - - - --
Data Souce

Jet OLEDB CompactW~lh False


Jet OLEDB Create System Fal,e
Jet OLEDB Dalabase Loc 1
Jet OLEDB Database Par
Jet OLEDB Don1Copy Lo Fdse
Jel OLEDB Enc~yplDalab Fdse
Jet OLEDB Engne Type 0
Jel OLEDB Globd B& TI 1
Jel OLEDB GlobdPalhd 2
Jet OLEDB New Database
Jei OLEDB Regdry Path
I.. n, ,-,.n.ci-n - +-.I-.

I I
Figura 15.4. Definicion de propiedades extendidas,

Por desgracia para 10s usuarios de Paradox, en algunos casos tendremos que
instalar el BDE ademas del motor Jet. Jet 4.0 necesita el BDE para poder actuali-
zar tablas Paradox, per0 no para solo leerlas. Lo mismo sucede en el caso de la
mayoria de las versiones de Paradox ODBC Driver. Microsoft ha recibido criti-
cas muy justificadas por este tema y ha creado un nuevo Paradox IISAM que no
necesita el BDE. Se pueden conseguir estos controladores actualizados gracias al
Servicio Tecnico de Microsoft.
-

NOTA: A medida que se conozca ADO en mayor profundidad, se descu-


brira c u h t o depende del proveedor OLE DB y del RDBMS (sistema de
administration de bases de datos relacionales) de que se trate. Aunque pue-
de usarse ADO con un fonnato de archivo local. como se mostrara en 10s
ejemplos siguientes, la idea general es instalar un motor SQL local siempre
que sea posible. Access y MSDE son buenas elecciones si se tiene que usar
ADO; de no ser asi, podrian tenerse en cuenta alternativas como InterBase
o Firebird, como se comenttr en el capitulo 14.

Excel a traves de Jet


Se puede acceder facilmente a Excel utilizando el proveedor Jet OLE DB. De
nuevo, usamos la propiedad E x t e n d e d P r o p e r t i e s y la definimos como Excel
8.0. Supongamos que tenemos una hoja de calculo de Excel llamada ABCCompany
.xls con una hoja llamada E m p l o y e e s , y que queremos abrir y leer este archivo
mediante Delphi. Con algo de conocimiento sobre COM, se puede hacer automatizan-
do Excel. Sin embargo, la solucion ADO es considerablemente mas sencilla de
implementar y no precisa que Excel se encuentre disponible en el ordenador.
- -

TRUCO: Tambidn se puede leer un archivo Excel mediante el componente


XLSReadWrite (disponible en www .axolot .corn). No requiere que Excel
se encuentre instalado en el ordenador ni el tiempo necesario para arrancar-
lo (corno hacen las tkaicas de OLE Automation).

Nos aseguraremos de que la hoja de calculo no este abierta en Escel, ya que


ADO necesita acceso exclusive a1 archivo. Aiiadimos un componente ADODataSet
a un formulario. Definimos su propiedad C o n n e c t i o n s t r i n g para usar el
proveedor Jet 4.0 OLE DB y definimos Estended Properties como Escel 8.0. En
la solapa Conexion, escribimos el nombre de la base de datos con la especifica-
cion completa de ruta y archivo de la hoja de calculo Excel (o usamos una ruta
relativa si tenemos planeado desplegar el archivo junto con el programa).
El componente ADODataSet funciona cuando abrimos o ejecutamos un valor
en su propiedad ComrnandText. Este valor podria ser el nombre de una tabla,
una sentencia SQL, un procedimiento almacenado o el nombre de un archivo.
Especificamos el mod0 en que se interpreta dicho valor estableciendo la propie-
dad CommandType.Definimos CommandType como cmdTableDirect para
indicar que el valor en CornrnandText se corresponde con el nombre de una
tabla y que todas las columnas deberian devolverse desde dicha tabla. Selecciona-
mos CommandText en el Object Inspector y veremos una flecha desplegable.
La desplegamos y aparecera una pseudo-tabla: Employees$. (Los libros de
Excel llevan como sufijo $ .)
Aiiadimos un Datasource y un DBGrid y 10s conectamos, con lo que obtendre-
mos el resultado del ejemplo JetExcel, que se muestra en la figura 15.5 en tiempo
de diseiio.
De manera predeterminada seria un poco dificil ver 10s datos en la cuadricula,
porque cada columna tiene 255 caracteres de ancho. Podemos cambiar el tamaiio
de visualizacion del campo aiiadiendo columnas a la cuadricula y cambiando sus
propiedades Width o aiiadiendo campos permanentes y cambiando sus propieda-
des Size o Displaywidth.

Dent + 55 41 338.5031
Ford Prelecl 141I 9957 0293
Marvin Robot (41 1 232.91 98
Tr~ll~an + 55 41 282 2399
BecMebrm 273-3522

A1
Figura 15.5. ABCCompany.xls en Delphi.

Fijese en que no podemos mantener abierto el conjunto de datos en tiempo de


diseiio y ejecutar el programa, ya que el controlador Excel IISAM abre el archivo
XSL en mod0 exclusivo. Por ello cerraremos el conjunto de datos y aiiadiremos a1
programa una linea para que lo abra durante el arranque.
Cuando se ejecute el programa, observaremos otra limitacion de este controla-
dor IISAM: podemos aiiadir nuevas filas y editar las que ya existen, per0 no
podemos borrarlas.
Por cierto, podria haberse utilizado un componente ADOTable o un ADOQuery,
en lugar del ADODataSet, pero es necesario conocer el mod0 en que ADO trata
10s simbolos en cosas como 10s nombres de tablas y campos. Si se usa un ADOTable
y se despliega la lista de tablas, se vera la tabla Employee$, como era de
esperar.
Lamentablemente, si se trata de abrir la tabla, se recibira un error. Lo mismo
sucede con la sentencia SELECT * FROM Employees$ en un ADOQuery. El
problema tiene que ver con el signo de dolar en el nombre de la tabla. Si se usan
caracteres como signos de d o h , puntos o, mucho mas importante, espacios en un
nombre de tabla o campo, entonces hay que encerrar el nombre entre corchetes
(por ejemplo, [Employees$]).
Archivos de texto a traves de Jet
Uno de 10s controladores IISAM mas utiles que se incluye con el motor Jet es
el Test IISAM. Este controlador permite leer y actualizar archivos de texto de
casi cualquier formato estructurado. Comenzaremos con un sencillo archivo de
texto y despues analizaremos algunas variaciones. Supongarnos que tenemos un
archivo de testo llamado NightShif t . TXT que contiene el siguiente testo:
Crewperson ,HomeTown
Neo ,Cincinnati
Trinity , London
Morpheus ,Milan

Aiiadimos un componente ADOTable a un formulario, definimos su


Connectionstring para usar el proveedor Jet 4.0 OLE DB y definimos
Extended Properties como Text. El Test IISAM considera un directorio como una
base de datos, por lo que hay que escribir como nombre de la base de datos el
directorio que contiene el archivo NightShif t .TXT.Volvemos a1 Object Ins-
pector y desplegamos la lista de tablas de la propiedad TableName.Observa-
remos que el punto del nombre del archivo se ha transformado en una almohadilla,
como en Night Shift#TXT.Establecemos Active como True,aiiadimos un
Datasource y un DBGrid y 10s conectamos, con lo que veremos el contenido del
archivo de texto en una cuadricula.

ADVERTENCIA: ~i la coofiguraci6n de nuestro ordenador cs tal que el


separador de decimales es una coma en lugar de un punto (de forma que
1,000.00 aparece como 1.000,00), entonces sera necesario cambiar la con-
figuration regional (Inicio>Configuraci6n>Panel de Control>Confi-
guracibn RegionabNrimem) o aprovecharse del uso de SCHEMA. I N I ,
como ahora verernos.

La cuadricula indica que el ancho de las columnas es de 255 caracteres. Pode-


mos cambiarlo igual que hicimos en el programa JetExcel a1 aiiadir campos o
columnas permanentes a la cuadricula y, a continuacion, definir las propiedades
de anchura correspondientes. Como alternativa, podemos definir la estructura del
archivo de forma mas especifica usando SCHEMA.INI.
En el ejemplo JetText, la carpeta de la base de datos se determina en tiempo de
ejecucion, segun cual sea la carpeta que contenga el programa. Para modificar la
cadena de conexion en tiempo de ejecucion, hay que cargarla en una lista de
cadena (despues de convertir 10s separadores) y usar la propiedad Values para
modificar uno solo de 10s elementos de la cadena de conesion. Este es el codigo
del ejemplo:
procedure TForml.FormCreate(Sender: TObject);
var
sl: TStringList;
begin
s l : = TStringList.Create;
sl.Text : = StringReplace (ADOTablel.ConnectionString,
. I ,
I , sLineBreak, [rfReplaceAll]);
s l .Values [ 'Data Source '1 : = ExtractFilePath
(App1ication.ExeName);
ADOTable1.ConnectionString : = StringReplace (sl.Text,
sLineBreak, '; ', [rfReplaceAll] ) ;
ADOTablel.Open;
sl. Free;
end;

Los archivos de texto pueden tener cualquier formato o tamaiio. Normalmente


no es necesario preocuparse por el formato de un archivo de texto porque Text
IISAM se fija en las primeras 25 filas para ver si puede determinar el formato por
si mismo.
Utiliza esta informacion y alguna otra adicional del Registro para decidir como
interpretar el archivo y como comportarse. Si tenemos un archivo que no se co-
rresponde con un formato habitual que pueda establecer el Text IISAM, entonces
podemos ofrecer esta informaci